Перевод: сумасшедший технический ботаник оригинал:blog.rising stack.com/PDF-from-contract…
В этой статье я покажу, как создавать PDF-документы из страниц React со сложным стилем, используя Node.js, Puppeteer, безголовый Chrome и Docker.
Предыстория: несколько месяцев назад клиент попросил нас разработать функцию, позволяющую пользователям получать содержимое страницы React в формате PDF. Страница в основном представляет собой отчет и визуализацию данных о пациенте с множеством SVG. Есть также некоторые специальные запросы на изменение макета и некоторую перестановку элементов HTML. Таким образом, в PDF должны быть другие стили и дополнительный контент по сравнению с исходной страницей React.
Поскольку эта задача намного сложнее, чем решение с помощью простых правил CSS, мы сначала изучили возможные реализации. Мы нашли 3 основных решения. Этот пост в блоге расскажет вам об их возможностях и возможной реализации.
содержание:
- Генерируется на стороне клиента или сервера?
- Сценарий 1: Скриншот DOM
- Решение 2. Используйте только библиотеку PDF
- Окончательное решение 3: Node.js, Puppeteer и Chrome без головы
- контроль стиля
- Отправьте файл клиенту и сохраните его
- Использование Puppeteer с Docker
- Сценарий 3 +1: правила печати CSS
- Суммировать
Генерируется на стороне клиента или сервера?
PDF-файлы могут быть созданы как на стороне клиента, так и на стороне сервера. Но может быть более разумным позволить бэкенду справиться с этим, поскольку вы не хотите исчерпать все ресурсы, которые может предоставить браузер пользователя.
Тем не менее, я покажу решения для обоих подходов.
Сценарий 1. Сделайте снимок экрана из DOM.
На первый взгляд это решение кажется самым простым, и так оно и есть, но оно имеет свои ограничения. Если у вас нет особых потребностей, таких как выделение текста в PDF или поиск текста, это простой в использовании метод.
Метод прост и понятен: создайте скриншот со страницы и поместите его в PDF-файл. Очень прямо вперед. Мы можем сделать это, используя два пакета:
- Html2canvas, генерировать скриншоты на основе DOM
- jsPdf, библиотека для создания PDF-файлов
Начать кодирование:
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')
})
Это оно!
осторожностьhtml2canvas
изonclone
метод. Это очень удобно, когда вам нужно манипулировать DOM перед тем, как сделать снимок экрана (например, скрыть кнопку печати). Я видел много проектов, использующих этот пакет. Но, к сожалению, это не то, что нам нужно, так как нам нужно сделать создание PDF на бэкенде.
Вариант 2. Используйте только библиотеку PDF
В NPM есть несколько библиотек, такие как JSPDF (как описано выше) илиPDFKit. Проблема с ними в том, что если я захочу использовать эти библиотеки, мне придется реструктурировать страницу. Это определенно ухудшает ремонтопригодность, поскольку мне нужно применить все последующие изменения к шаблону PDF и странице React.
Пожалуйста, смотрите код ниже. Вам необходимо вручную создать документ PDF самостоятельно. Вам нужно просмотреть DOM, найти каждый элемент и преобразовать его в PDF, что является утомительной работой. Должен быть более простой способ.
doc = new PDFDocument
doc.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: [250, 300],
align: 'center',
valign: 'center'
});
doc.addPage()
.fontSize(25)
.text('Here is some vector graphics...', 100, 100)
doc.end()
Этот фрагмент кода взят из документации PDFKit. Но это все равно полезно, если вашей целью является непосредственное создание PDF-файла, а не преобразование существующей (и изменяемой) HTML-страницы.
Окончательное решение 3: Puppeteer на основе Node.js и Headless Chrome
чтоPuppeteer? В его документации говорится:
Puppeteer Node — это библиотека, предоставляющая высокоуровневый API для управления Chrome или Chromium по соглашению DevTools. Режим по умолчанию в безголовом Puppeteer Chrome или Chromium, но его также можно настроить для работы в полном (не безголовом) режиме.
По сути, это браузер, который можно запустить из Node.js. Если вы читали его документацию, первое упоминание в ней — это то, что вы можете использовать Puppeteer дляСоздание скриншотов и PDF-файлов страниц. превосходно! Это именно то, что мы хотим.
Сначала сnpmi i puppeteer
Установите Puppeteer и реализуйте наши возможности.
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
})
Это простая функция, которая переходит по URL-адресу и создает PDF-файл сайта.
Сначала мы запускаем браузер (генерация PDF поддерживается только в автономном режиме), затем открываем новую страницу, устанавливаем область просмотра и переходим по указанному URL-адресу.
настраиватьwaitUntil:'networkidle0'
Опция означает, что Puppeteer будет считать навигацию завершенной при отсутствии сетевого подключения в течение как минимум 500 мс. (Доступна сAPI docsПолучите больше информации. )
После этого сохраняем PDF как переменную, закрываем браузер и возвращаемся к PDF.
Уведомление:page.pdf
метод полученияoptions
объект, вы можете использовать опцию «путь», чтобы сохранить файл на диск. Если путь не указан, PDF-файл не будет сохранен на диск, а будет помещен в буфер. (Я расскажу, как с этим бороться позже.)
Если для создания PDF-файла с защищенной страницы требуется вход в систему, сначала вы перейдете на страницу входа, проверите идентификатор или имя элементов формы, заполните их и отправьте форму:
await page.type('#email', process.env.PDF_USER)
await page.type('#password', process.env.PDF_PASSWORD)
await page.click('#submit')
Чтобы всегда сохранять учетные данные для входа в переменные среды, не используйте жесткий код!
контроль стиля
У Puppeteer также есть решение для этой манипуляции со стилем. Вы можете вставить теги стилей перед созданием PDF-файла, и Puppeteer создаст файл с измененными стилями.
await page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' })
Отправьте файл клиенту и сохраните его
Хорошо, теперь вы создали файл PDF в бэкэнде. Что дальше?
Как упоминалось выше, если вы не сохраните файл на диск, вы получите буфер. Вам просто нужно отправить буфер с соответствующим типом контента во внешний интерфейс.
printPDF.then(pdf => {
res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdf.length })
res.send(pdf)
Теперь вам нужно только отправить запрос на сервер в браузере, чтобы получить сгенерированный PDF.
function getPDF() {
return axios.get(`${API_URL}/your-pdf-endpoint`, {
responseType: 'arraybuffer',
headers: {
'Accept': 'application/pdf'
}
})
После отправки запроса содержимое буфера должно начать загружаться. Последним шагом является преобразование данных буфера в файл PDF.
savePDF = () => {
this.openModal(‘Loading…’) // open modal
return getPDF() // API call
.then((response) => {
const blob = new Blob([response.data], {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>
Это оно! Если вы нажмете кнопку «Сохранить», браузер сохранит PDF-файл.
Использование Puppeteer с Docker
Я думаю, что это самая сложная часть реализации, так что позвольте мне сэкономить вам несколько часов времени Baidu.
Официальная документация гласит * «Использование безголового Chrome в Docker и его запуск могут быть сложными» *. В официальной документации естьПоиск проблемыраздел, вы можете найти всю необходимую информацию об установке puppeteer с помощью Docker.
Если вы устанавливаете Puppeteer на образ Alpine, обязательно посмотритеэта часть страницыПрокрутите вниз еще немного. В противном случае вы можете упустить из виду тот факт, что вы не можете запустить последнюю версию Puppeteer, а также вам необходимо отключить shm с помощью флага:
const browser = await puppeteer.launch({
headless: true,
args: ['--disable-dev-shm-usage']
});
В противном случае дочерним процессам Puppeteer может не хватить памяти до нормального запуска.
Сценарий 3 + 1: правила печати CSS
Можно подумать, что с точки зрения разработчика просто использовать CSS для печати правил — это просто. Никаких модулей NPM, только чистый CSS. Но как обстоят дела с кросс-браузерной совместимостью?
При выборе правила печати CSS вы должны проверить результаты в каждом браузере, чтобы убедиться, что макет, который он предоставляет, одинаков, и он не может сделать это на 100%.
Например, вставка разрыва после заданного элемента — не очень сложная техника, но вы можете быть удивлены, обнаружив, чтоИспользуйте его в FirefoxТребуется обходной путь.
Если вы не являетесь опытным гуру CSS с большим опытом создания печатных страниц, это может занять много времени.
Правила печати хороши, если вы можете сделать свои таблицы стилей печати простыми.
Давайте посмотрим на пример.
@media print {
.print-button {
display: none;
}
.content div {
break-after: always;
}
}
Приведенный выше CSS скрывает кнопку печати и печатает ее каждый раз.div
Вставьте разрыв страницы после этого, который содержитcontent
Добрый. Есть одинотличная статьяОбобщает, что вы можете делать с правилами печати и что с ними не так, включая совместимость с браузером.
Учитывая все обстоятельства, правила печати CSS работают очень хорошо, если вы хотите создавать PDF-файлы из менее сложных страниц.
Суммировать
Давайте быстро рассмотрим ранее представленные сценарии создания PDF-файлов из HTML-страниц:
-
Создать скриншот из DOM: может быть полезно, когда вам нужно создать снимки страницы (например, для создания эскизов), но может быть немного растянутым, когда вам нужно обработать много данных.
-
Используйте только библиотеку PDF: это идеальное решение, если вы планируете программно создавать PDF-файлы с нуля. В противном случае вам необходимо поддерживать как HTML, так и PDF-шаблоны, что, безусловно, недопустимо.
-
Puppeteer: Несмотря на то, что работать с Docker было относительно сложно, он дал нашей реализации наилучшие результаты, а также его было легче всего кодировать.
-
Правила печати CSS: если ваши пользователи достаточно образованы, чтобы знать, как печатать содержимое страницы в файл, и ваша страница относительно проста, то это, вероятно, самое простое решение. Как видите, в нашем случае это не так.
Хотя сегодня День дурака, все вышесказанное верно!
С Первым Апреля!
Добро пожаловать на официальный аккаунт Jingcheng Yideng: Jingcheng Yideng, чтобы получить больше галантереи.