PDFs in Node.js erstellen: Puppeteer, PDFKit und jsPDF
Mit Puppeteer drucken Sie einen HTML-Bericht, mit PDFKit zeichnen Sie ein Dokument aus Daten, oder Sie laden mit jsPDF und html2canvas ein Bild eines Browser-Elements herunter. Diese Open-Source-Beispiele erzeugen PDFs mit unterschiedlichen Text- und Layouteigenschaften, die bei der Wahl einer Exportmethode entscheidend sind.
Hier geht es um PDF-Erstellung, nicht um allgemeine Dokumentkonvertierung: Keines dieser Beispiele wandelt Word-, Tabellenkalkulations- oder PDF-Dateien in andere bearbeitbare Formate um.
Die richtige Bibliothek wählen
| Ihre Eingabe | Beispiel | Erwartete Ausgabe |
|---|---|---|
| Lokales HTML mit CSS | Puppeteer | Ein A4-Drucklayout mit auswählbarem Text |
| Zeichenfolgen und Zeichenanweisungen | PDFKit | Ein A4-Dokument mit auswählbarem Text; Sie legen die Koordinaten fest |
| Ein Browser-Element | jsPDF + html2canvas | Ein Bild, das auf eine A4-Seite eingepasst wird, ohne auswählbaren Text |
Beispiele einrichten
Diese Beispiele wurden unter Linux mit Node.js 26.8.1,
Yarn 4.12.0 und Puppeteers Chrome for Testing 151.0.7922.71 getestet. Verwenden Sie diese getesteten
Versionen, um die Anleitung nachzuvollziehen; sie sind keine Mindestanforderungen der Bibliotheken.
Corepack und unzip müssen installiert sein, bevor Sie das Projekt erstellen.
Folgen Sie der Installationsanleitung für Yarn,
falls corepack --version fehlschlägt.
Chrome benötigt außerdem die Systembibliotheken, die in den
Systemanforderungen von Puppeteer aufgeführt sind.
Fügen Sie dies in Bash ein, während Sie sich in einem beschreibbaren Verzeichnis befinden.
Es erstellt ein separates Projekt mit lokalen Paket- und Browser-Caches und kehrt anschließend
in Ihr ursprüngliches Verzeichnis zurück. Ein bereits vorhandenes node-pdf-demo
bleibt unverändert. Falls die Installation nach der Erstellung fehlschlägt, beheben Sie das
angezeigte Problem und wiederholen Sie dann die beiden Befehle corepack yarn
in diesem Ordner; Sie müssen ihn nicht neu erstellen.
(
corepack --version &&
unzip -v >/dev/null &&
mkdir node-pdf-demo &&
cd node-pdf-demo &&
printf '%s\n' '{"private":true,"packageManager":"yarn@4.12.0"}' > package.json &&
touch yarn.lock &&
printf '%s\n' 'nodeLinker: node-modules' 'enableGlobalCache: false' > .yarnrc.yml &&
PUPPETEER_CACHE_DIR="$PWD/.puppeteer" corepack yarn add --exact \
puppeteer@25.5.0 pdfkit@0.17.2 jspdf@4.2.1 html2canvas@1.4.1 &&
PUPPETEER_CACHE_DIR="$PWD/.puppeteer" corepack yarn exec puppeteer browsers install chrome
)
Die explizite Browser-Installation funktioniert auch, wenn Installationsskripte von Abhängigkeiten
deaktiviert sind. Puppeteers Installationsanleitung
erklärt diesen Unterschied. Speichern Sie die folgenden Dateien in node-pdf-demo
und führen Sie die Node-Befehle aus diesem Verzeichnis aus. Die Dateiendung
.cjs sorgt dafür, dass die Server-Beispiele auch innerhalb eines
ES-Modul-Projekts CommonJS verwenden.
HTML-zu-PDF-Konvertierung mit Puppeteer
Puppeteer verwendet den Druckrenderer von Chrome. Seine
Methode page.pdf()
verwendet standardmäßig Druck-CSS, sodass ein PDF vom Bildschirmlayout abweichen kann. Dieses
Beispiel verwendet ausschließlich lokales HTML ohne externe Abhängigkeiten. Rendern Sie HTML,
das Sie kontrollieren; dies ist kein Dienst zur Annahme beliebiger URLs oder nicht
vertrauenswürdiger Skripte.
Einen lokalen Bericht drucken
Speichern Sie dies als report.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Puppeteer report</title>
<style>
body { margin: 0; font-family: Arial, sans-serif; }
main { padding: 32px; }
h1 { font-size: 28px; }
.marker { height: 24px; background: #c81e50; }
footer { margin-top: 160px; }
</style>
</head>
<body>
<main>
<h1>Hello, Puppeteer!</h1>
<p>This report keeps its text selectable.</p>
<div class="marker"></div>
<footer>End of report.</footer>
</main>
</body>
</html>
Speichern Sie dies als html-to-pdf.cjs:
const { pathToFileURL } = require('node:url')
const puppeteer = require('puppeteer')
async function htmlToPdf(url, outputPath) {
const browser = await puppeteer.launch()
try {
const page = await browser.newPage()
const response = await page.goto(url, { waitUntil: 'networkidle0' })
if (!response || !response.ok()) {
throw new Error(`Page load failed: ${response?.status() ?? 'no HTTP response'}`)
}
await page.pdf({
path: outputPath,
format: 'A4',
printBackground: true,
margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
})
} finally {
await browser.close()
}
}
const inputUrl = pathToFileURL(process.argv[2] ?? 'report.html').href
htmlToPdf(inputUrl, process.argv[3] ?? 'puppeteer.pdf').catch((error) => {
console.error(error.message)
process.exitCode = 1
})
PUPPETEER_CACHE_DIR="$PWD/.puppeteer" node html-to-pdf.cjs report.html puppeteer.pdf
Öffnen Sie puppeteer.pdf. Die Datei sollte eine Seite, einen farbigen Balken und
End of report. unter dem Fließtext enthalten.
Wählen Sie Hello, Puppeteer! aus und kopieren Sie es,
um zu prüfen, ob es sich um Text statt um ein Bild handelt.
printBackground: true schließt den CSS-Balken ein. Längeres HTML fließt über mehrere
Druckseiten; ergänzen Sie Druckstile für Ihre eigenen Seitenumbrüche. Eine dynamische Seite braucht
eine eigene Bedingung, die ihre Bereitschaft bestätigt, da ausbleibende Netzwerkaktivität nicht
belegt, dass alle Aktualisierungen der Anwendung abgeschlossen sind.
PDFs mit PDFKit erstellen
PDFKit erstellt Inhalte über seine Zeichen- und Text-APIs; es interpretiert Ihr HTML oder CSS nicht. Verwenden Sie es, wenn Sie Daten haben und deren Position auf der Seite bestimmen möchten. Die Einstiegsanleitung beschreibt den Dokumentstream und die Seitenoptionen.
Ein Dokument zeichnen
Speichern Sie dies als create-pdf.cjs. Durch das Warten auf
pipeline() werden Fehler des Ausgabestreams gemeldet, bevor das Programm endet:
const PDFDocument = require('pdfkit')
const fs = require('node:fs')
const { pipeline } = require('node:stream/promises')
async function createPdf(outputPath) {
const doc = new PDFDocument({ size: 'A4' })
const writing = pipeline(doc, fs.createWriteStream(outputPath))
try {
doc.fontSize(25).text('Hello, PDFKit!', 100, 100)
doc.fontSize(12).text('This is a sample PDF generated using PDFKit.', 100, 150)
doc.text('End of report.', 100, 350)
doc.end()
} catch (error) {
doc.destroy(error)
}
await writing
}
createPdf(process.argv[2] ?? 'pdfkit.pdf').catch((error) => {
console.error(error.message)
process.exitCode = 1
})
node create-pdf.cjs pdfkit.pdf
Öffnen Sie pdfkit.pdf und versuchen Sie,
Hello, PDFKit! aus der Überschrift zu kopieren.
Die drei Textzeilen sollten auf eine A4-Seite passen. Die Koordinaten werden in PDF-Punkten
angegeben, mit 72 Punkten pro Zoll. PDFKit kann Text umbrechen, doch ein Dokument mit Tabellen oder
wiederkehrenden Kopfzeilen braucht weiterhin Layoutlogik; eine HTML-Vorlage lässt sich nicht auf
diese APIs übertragen.
Beide Node-Befehle überschreiben ein vorhandenes Ziel, sobald das Schreiben beginnt. Eine fehlende Eingabe oder ein fehlgeschlagener Browserstart kann ein früheres PDF unverändert lassen, und ein fehlgeschlagener Schreibvorgang kann eine unvollständige Datei hinterlassen. Verwenden Sie einen neuen Dateinamen, wenn Sie einen früheren Export behalten müssen. Um die Fehlerbehandlung zu prüfen, geben Sie als Ausgabe einen Pfad innerhalb eines nicht existierenden Verzeichnisses an; der Befehl sollte einen Fehler melden und mit einem Exit-Code ungleich null enden.
Clientseitige PDF-Erstellung mit jsPDF
jsPDF kann auch textbasierte PDFs erstellen. Hier rendert html2canvas ein HTML-Element auf einen Canvas, den jsPDF als Bild einbettet. html2canvas rekonstruiert das Element anhand unterstützter DOM- und CSS-Eigenschaften; die Dokumentation erklärt, warum das Ergebnis von einem Browser-Screenshot abweichen kann.
Ein Bild eines Browser-Elements herunterladen
Speichern Sie diese vollständige Datei als browser-pdf.html neben
node_modules und öffnen Sie sie in Chrome:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Browser PDF export</title>
<style>
#content { box-sizing: border-box; width: 640px; padding: 32px; font: 16px Arial; }
h1 { font-size: 28px; }
.marker { height: 24px; background: #c81e50; }
footer { margin-top: 160px; }
</style>
</head>
<body>
<script src="./node_modules/jspdf/dist/jspdf.umd.min.js"></script>
<script src="./node_modules/html2canvas/dist/html2canvas.min.js"></script>
<main id="content">
<h1>Hello, jsPDF!</h1>
<p>This report is exported as an image.</p>
<div class="marker"></div>
<footer>End of report.</footer>
</main>
<button id="download">Download PDF</button>
<p id="status" role="status"></p>
<script>
async function generatePDF() {
const { jsPDF } = window.jspdf
const content = document.getElementById('content')
const canvas = await html2canvas(content, { scale: 2, backgroundColor: '#ffffff' })
const imgData = canvas.toDataURL('image/png')
const pdf = new jsPDF()
const width = pdf.internal.pageSize.getWidth() - 20
const height = pdf.internal.pageSize.getHeight() - 20
const scale = Math.min(width / canvas.width, height / canvas.height)
pdf.addImage(imgData, 'PNG', 10, 10, canvas.width * scale, canvas.height * scale)
await pdf.save('download.pdf', { returnPromise: true })
}
const button = document.getElementById('download')
button.addEventListener('click', async () => {
if (button.disabled) return
button.disabled = true
const status = document.getElementById('status')
status.textContent = ''
try {
await generatePDF()
status.textContent = 'Download requested.'
} catch {
status.textContent = 'Unable to generate the PDF. Please try again.'
} finally {
button.disabled = false
}
})
</script>
</body>
</html>
Klicken Sie auf Download PDF. Die Schaltfläche
bleibt deaktiviert, während das Bild erstellt wird. Anschließend zeigt die Seite
Download requested.
Diese Meldung bedeutet, dass die Speicheranfrage ausgelöst wurde; suchen Sie in den Downloads
Ihres Browsers nach download.pdf.
Das jsPDF-Projekt dokumentiert seine Browser-Version.
Öffnen Sie die heruntergeladene Datei und prüfen Sie Überschrift, farbigen Balken und Fußzeile. Alle sollten innerhalb der Seitenränder sichtbar sein, doch die Überschrift lässt sich nicht als Text auswählen. Dieses Beispiel skaliert das gesamte Bild auf eine Seite, statt es auf mehrere Seiten aufzuteilen. Lange Berichte werden dadurch klein dargestellt. Verwenden Sie Puppeteer oder ein textbasiertes Layout für durchsuchbaren Text. Dem Bild fehlt außerdem die semantische Struktur des ursprünglichen HTML für Screenreader. Externe Bilder benötigen eine kompatible Konfiguration für denselben Ursprung oder CORS; dieses Beispiel verwendet weder Bilder noch Webfonts.
wkhtmltopdf auf Legacy-Workflows beschränken
wkhtmltopdf wandelt HTML mithilfe einer alten Qt-WebKit-Engine in PDF um. Sein Repository wurde im Januar 2023 archiviert, und es wird nicht mehr gepflegt. Beschränken Sie bestehende Installationen auf vertrauenswürdiges HTML. Die Sicherheitshinweise des Projekts warnen vor nicht vertrauenswürdigen Eingaben. Testen Sie bei einer bestehenden Installation Ihre eigenen vertrauenswürdigen Vorlagen mit genau der dort verwendeten Binärdatei und den verwendeten Schriftarten. Die obigen Beispiele validieren diesen Renderer nicht. Verwenden Sie für neue HTML-zu-PDF-Aufgaben einen gepflegten Renderer wie Puppeteer.
