Générer des PDF dans Node.js : Puppeteer, PDFKit et jsPDF
Utilisez Puppeteer pour imprimer un rapport HTML, PDFKit pour dessiner un document à partir de données, ou jsPDF avec html2canvas pour télécharger une image d’un élément du navigateur. Ces exemples open source produisent des PDF aux propriétés différentes pour le texte et la mise en page, ce qui compte lorsque vous choisissez une méthode d’export.
Il s’agit de génération de PDF, pas de conversion générale de documents : aucun de ces exemples ne convertit des fichiers Word, des feuilles de calcul ou des PDF en d’autres formats modifiables.
Choisir la bonne bibliothèque
| Votre entrée | Exemple | Résultat attendu |
|---|---|---|
| HTML local avec CSS | Puppeteer | Une mise en page A4 pour l’impression avec du texte sélectionnable |
| Chaînes de caractères et instructions de dessin | PDFKit | Un document A4 avec du texte sélectionnable ; vous définissez les coordonnées |
| Un élément du navigateur | jsPDF + html2canvas | Une image ajustée à une seule page A4, sans texte sélectionnable |
Préparer les exemples
Ces exemples ont été testés sous Linux avec Node.js 26.8.1,
Yarn 4.12.0 et Chrome for Testing 151.0.7922.71 de Puppeteer. Utilisez ces versions testées pour
reproduire ce guide ; elles ne correspondent pas aux versions minimales requises par les
bibliothèques. Corepack et unzip doivent être installés avant de créer
le projet ; suivez le guide d’installation de Yarn si
corepack --version échoue.
Chrome a également besoin des bibliothèques système indiquées dans la
configuration système requise pour Puppeteer.
Collez ce qui suit dans Bash depuis un répertoire accessible en écriture. Cela crée un projet
séparé avec des caches locaux pour les paquets et le navigateur, puis vous ramène à votre
répertoire d’origine. Un node-pdf-demo existant reste intact. Si l’installation
échoue après la création, corrigez le problème signalé, puis répétez les deux commandes
corepack yarn dans ce dossier ; vous n’avez pas besoin de le recréer.
(
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
)
L’installation explicite du navigateur fonctionne même lorsque les scripts d’installation des
dépendances sont désactivés. Le guide d’installation de Puppeteer
explique cette distinction. Enregistrez les fichiers ci-dessous dans
node-pdf-demo, puis exécutez les commandes Node depuis ce répertoire.
L’extension .cjs maintient les exemples côté serveur au format CommonJS,
même dans un projet utilisant les modules ES.
Conversion de HTML en PDF avec Puppeteer
Puppeteer utilise le moteur de rendu d’impression de Chrome. Sa
méthode page.pdf() utilise par défaut le CSS
d’impression ; un PDF peut donc différer de la mise en page à l’écran. Cet exemple utilise
uniquement du HTML local et autonome. Effectuez le rendu de HTML que vous contrôlez ; il ne s’agit
pas d’un service acceptant des URL arbitraires ou des scripts non fiables.
Imprimer un rapport local
Enregistrez ceci sous le nom 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>
Enregistrez ceci sous le nom 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
Ouvrez puppeteer.pdf. Il devrait contenir une page, une barre colorée et
End of report. sous le corps du texte.
Sélectionnez et copiez Hello, Puppeteer! pour vérifier
qu’il s’agit de texte plutôt que d’une image. printBackground: true inclut la barre CSS.
Un contenu HTML plus long se répartit sur plusieurs pages à l’impression ; ajoutez des styles
d’impression pour définir vos propres sauts de page. Une page dynamique nécessite sa propre
condition indiquant qu’elle est prête, car l’inactivité du réseau ne prouve pas que toutes les
mises à jour de l’application sont terminées.
Générer des PDF avec PDFKit
PDFKit crée du contenu via ses API de dessin et de texte ; il n’interprète pas votre HTML ou CSS. Utilisez-le lorsque vous avez des données et souhaitez contrôler leur emplacement sur la page. Son guide de démarrage décrit le flux du document et les options de page.
Dessiner un document
Enregistrez ceci sous le nom create-pdf.cjs. L’attente de
pipeline() permet de signaler les erreurs du flux de sortie avant la fin du
programme :
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
Ouvrez pdfkit.pdf et essayez de copier
Hello, PDFKit! depuis le titre.
Les trois lignes de texte devraient tenir sur une page A4. Les coordonnées sont exprimées en
points PDF, à raison de 72 points par pouce. PDFKit peut répartir le texte sur plusieurs lignes,
mais un document contenant des tableaux ou des en-têtes répétés nécessite toujours une logique
de mise en page ; un modèle HTML ne se transpose pas à ces API.
Les deux commandes Node remplacent un fichier de destination existant dès le début de l’écriture. Une entrée manquante ou un échec du lancement du navigateur peuvent laisser un PDF antérieur intact, et un échec de l’écriture peut laisser un fichier partiel. Utilisez un nouveau nom de fichier si vous devez conserver un export antérieur. Pour vérifier la gestion des erreurs, indiquez en sortie un chemin dans un répertoire inexistant ; la commande devrait signaler une erreur et se terminer avec un code de sortie non nul.
Génération de PDF côté client avec jsPDF
jsPDF peut également créer des PDF contenant du texte. Ici, html2canvas effectue le rendu d’un élément HTML dans un canvas, puis jsPDF intègre ce canvas sous forme d’image. html2canvas reconstruit l’élément à partir des propriétés DOM et CSS prises en charge ; sa documentation explique pourquoi le résultat peut différer d’une capture d’écran du navigateur.
Télécharger une image d’un élément du navigateur
Enregistrez ce fichier complet sous le nom browser-pdf.html, à côté de
node_modules, puis ouvrez-le dans 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>
Cliquez sur Download PDF. Le bouton reste
désactivé pendant la génération de l’image, puis la page affiche
Download requested.
Ce message signifie que la demande d’enregistrement a été émise ; recherchez
download.pdf dans les téléchargements de votre navigateur.
Le projet jsPDF documente sa version pour navigateur.
Ouvrez le fichier téléchargé et vérifiez le titre, la barre colorée et le pied de page. Ils devraient tous être visibles à l’intérieur des marges de la page, mais vous ne pouvez pas sélectionner le titre comme du texte. Cet exemple ajuste l’image entière à une seule page au lieu de la répartir sur plusieurs pages ; les rapports longs deviennent donc petits. Utilisez Puppeteer ou une mise en page fondée sur du texte pour permettre la recherche dans le texte. L’image ne conserve pas non plus la structure sémantique du HTML d’origine pour les lecteurs d’écran. Les images externes nécessitent une configuration compatible de même origine ou CORS ; cet exemple n’utilise ni images ni polices web.
Limiter wkhtmltopdf aux anciens flux de travail
wkhtmltopdf convertit du HTML en PDF à l’aide d’un ancien moteur Qt WebKit. Son dépôt a été archivé en janvier 2023, et il n’est plus maintenu. Limitez les installations existantes au HTML de confiance. Les consignes de sécurité du projet mettent en garde contre les entrées non fiables. Pour une installation existante, testez vos propres modèles de confiance avec le binaire et les polices exacts de cette installation. Les exemples ci-dessus ne valident pas ce moteur de rendu. Utilisez un moteur de rendu maintenu, tel que Puppeteer, pour les nouveaux travaux de conversion de HTML en PDF.
