Gerando PDFs utilizando React e Puppeteer
Nesse artigo passaremos por todas as etapas necessárias para se criar um PDF de uma página estática utilizando React e Puppeteer.
Todo o código que utilizaremos nesse projeto está disponível no Github .
인디스
1. 꼭두각시 인형을 조종해야 하나요?
O Puppeteer é uma biblioteca do Node que provê uma API de controle do Chrome de maneira headless, ou seja, apenas em memória e sem a necessidade de visualmente existir um browser na tela. Esse tipo de abordagem permite utilizar o navegador em um script como vamos fazer mais à frente. Muitas vezes também é utilizado em testes end-to-end e em scrapers.
Uma das vantagens de se utilizar o Puppeteer para geração dos PDFs é que, como resultado, teremos um PDF real, vetorizado e com alta qualidade de impressão. Algo que não conseguimos com outros métodos que utilizam screenshots para geração dos documentos.
Para mais informações acesse a documentação oficial: https://pptr.dev/2. 크리안도 오 프로젝트
Como primeiro passo criaremos nosso projeto React que servirá de base para a criação dos PDFs.
Obs: Caso você já tenha um projeto criado fique à vontade para pular essa etapa, mas lembre-se, as medidas de sua página React usada na impressão devem ser as mesmas do script do Puppeteer que será configurado mais à frente.
Como exemplo, criaremos uma página contendo gráficos e conteúdos de texto. Podemos iniciar nosso projeto de maneira rápida criando um setup inicial com o create-react-app.
npx create-react-app pdf-puppeteer
cd pdf-puppeteer
yarn start
Remova os arquivos logo.svg
, App.css
e App.test.js
. Troque o código em src/App.js
pelo código abaixo. É extremamente importante que a tela que será acessada para criação do PDF tenha medidas iguais as que forem configuradas no script do Puppeteer.
Repare que no código utilizamos as medidas em milímetros de uma página A4. Fique à vontade para alterar o tamanho da maneira que achar necessário, sempre garantindo que as medidas estejam iguais às usadas no Puppeteer e sigam a proporção do tamanho de página escolhido.
import logo from './logo.svg';
import Chart from './components/Chart';
const chartData = [
{
name: 'Item 1',
value: 51.1,
},
{
name: 'Item 2',
value: 28.9,
},
{
name: 'Item 3',
value: 20,
},
{
name: 'Item 4',
value: 70.1,
},
{
name: 'Item 5',
value: 34.7,
},
]
function App() {
return (
<div
style={{
width: '209.55mm',
height: '298.45mm',
padding:'12mm',
backgroundColor: '#FFF',
}}
>
{/** Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
<div>
<h1>Data Report</h1>
<h2>{new Date().getYear() + 1900}</h2>
</div>
<img src={logo} className="App-logo" alt="logo" style={{ width: '50mm', height: '50mm'}}/>
</div>
{/** Introduction text */}
<h3>Introduction</h3>
<h5 style={{ fontWeight: 'normal' }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dolor sit amet consectetur adipiscing elit duis tristique sollicitudin. Commodo viverra maecenas accumsan lacus vel facilisis volutpat est velit. Ut eu sem integer vitae. Bibendum neque egestas congue quisque egestas diam in. Quis lectus nulla at volutpat diam. Cursus euismod quis viverra nibh. Amet consectetur adipiscing elit duis tristique sollicitudin nibh sit amet. Nibh sed pulvinar proin gravida hendrerit lectus a. Purus in massa tempor nec feugiat nisl pretium. Velit dignissim sodales ut eu sem integer vitae justo eget. Augue ut lectus arcu bibendum at varius. Interdum varius sit amet mattis vulputate enim. In hendrerit gravida rutrum quisque non tellus orci. Lectus nulla at volutpat diam ut venenatis. Massa tempor nec feugiat nisl pretium fusce id velit ut. Aliquet sagittis id consectetur purus ut faucibus. Eget mi proin sed libero enim.
</h5>
{/** Chart with title */}
<h3>Chart 01</h3>
<Chart
data={chartData}
barProps={{
isAnimationActive: false,
}}
/>
{/** Info text */}
<h5 style={{ fontWeight: 'normal' }}>
Pulvinar pellentesque habitant morbi tristique senectus et netus. Nunc eget lorem dolor sed viverra ipsum nunc aliquet bibendum. Enim ut tellus elementum sagittis vitae et leo duis ut. Adipiscing vitae proin sagittis nisl. Orci phasellus egestas tellus rutrum tellus pellentesque eu tincidunt tortor. Id nibh tortor id aliquet lectus proin nibh nisl condimentum. Platea dictumst vestibulum rhoncus est pellentesque. Dictum sit amet justo donec enim diam vulputate. Libero volutpat sed cras ornare arcu dui. Magna fermentum iaculis eu non diam.
</h5>
{/** Chart with title */}
<h3>Chart 02</h3>
<Chart
data={chartData}
barProps={{
isAnimationActive: false,
}}
/>
</div>
);
}
export default App;
Será necessário também criar o arquivo src/components/Chart.jsx
contendo o componente de gráfico que utilizaremos no exemplo. Precisamos também instalar a biblioteca Recharts, uma excelente opção para criação de gráficos em SVG para React.
yarn add recharts
import React from 'react';
import PropTypes from 'prop-types';
import {
BarChart,
Bar,
XAxis,
YAxis,
ReferenceLine,
ResponsiveContainer,
} from 'recharts';
const CustomBarLabel = ({ isPercentage, labelProps, ...props }) => {
const {
x, y, value, width, height,
} = props;
const xPosition = width >= 0 ? x + width + 4 : x + width - ((value === -100 || value % 1 !== 0) ? 27 : 20);
const yPosition = y + height / 2 + 6;
return (
<text
dy={-4}
x={xPosition}
y={yPosition}
textAnchor='right'
fontSize={12}
fontWeight='600'
fill='#4D5365'
fontFamily='Helvetica'
{...labelProps}
>
{isPercentage ? `${value.toFixed(1).replace(/\.0$/, '')}%` : value.toFixed(1).replace(/\.0$/, '')}
</text>
);
};
CustomBarLabel.propTypes = {
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
value: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
isPercentage: PropTypes.bool,
labelProps: PropTypes.object,
};
CustomBarLabel.defaultProps = {
isPercentage: false,
labelProps: {},
};
const Chart = ({
data,
range,
width,
height,
barSize,
barProps,
xAxisProps,
yAxisProps,
barChartProps,
labelProps,
isPercentage,
legend,
children,
}) => {
const { min, max, step } = range;
const ticks = (max - min) / step + 2;
const addLines = (start, end, arrayStep = 1) => {
const len = Math.floor((end - start) / arrayStep) + 1;
return Array(len).fill().map((_, idx) => start + (idx * arrayStep));
};
return (
<ResponsiveContainer width={width} height={height}>
<BarChart
data={data}
margin={{
top: 0, right: 0, left: 10, bottom: 0,
}}
layout='vertical'
barSize={barSize}
{...barChartProps}
>
<XAxis
type='number'
tickCount={ticks}
orientation='top'
domain={[min, max]}
axisLine={false}
tickLine={false}
tick={{
fill: '#6F798B',
fontSize: 14,
fontFamily: 'Helvetica',
}}
{...xAxisProps}
/>
<YAxis
dx={-16}
type='category'
dataKey='name'
axisLine={false}
tickLine={false}
tick={{
fill: '#4D5365',
fontSize: 16,
lineHeight: 22,
fontFamily: 'Helvetica',
}}
interval={0}
{...yAxisProps}
/>
{addLines(min, max, step).map((item) => (
<ReferenceLine key={item} x={item} style={{ fill: '#CDD2DB' }} />
))}
<Bar
dataKey='value'
fill='#A35ADA'
label={(props) => <CustomBarLabel isPercentage={isPercentage} labelProps={labelProps} {...props} />}
{...barProps}
/>
{children}
</BarChart>
</ResponsiveContainer>
);
};
Chart.propTypes = {
data: PropTypes.array,
range: PropTypes.shape({
min: PropTypes.number,
max: PropTypes.number,
step: PropTypes.number,
}),
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
barSize: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
barProps: PropTypes.object,
xAxisProps: PropTypes.object,
yAxisProps: PropTypes.object,
barChartProps: PropTypes.object,
labelProps: PropTypes.object,
isPercentage: PropTypes.bool,
legend: PropTypes.bool,
children: PropTypes.any,
};
Chart.defaultProps = {
data: [{ name: null, value: null }],
range: {
min: 0,
max: 100,
step: 20,
},
width: '100%',
height: 254,
barSize: 22,
barProps: {},
xAxisProps: {},
yAxisProps: {},
barChartProps: {},
labelProps: {},
isPercentage: false,
legend: false,
children: null,
};
export default Chart;
3. Criando o script do Puppeteer
Para utilizar o Puppeteer precisaremos instalar três pacotes:
- Puppeteer: pacote com o Chrome headless que será utilizado para geração dos PDFs
- Babel-Core: Usado para converter versões recentes do Javascript para ambientes e navegadores mais antigos.
- Babel-Node: CLI que funciona da mesma forma que o Node.js, com a vantagem de compilar códigos ES6 usando o Babel.
Rode o comando no terminal dentro da pasta do projeto para instalar os pacotes necessários:
yarn add -D @babel/core @babel/node puppeteer
Com os pacotes adicionados podemos criar nosso script no arquivo src/generate.js
com o código abaixo.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:3000');
await page.pdf({
path: 'src/assets/puppeteer-test.pdf',
printBackground: true,
width: '209.55mm',
height: '298.45mm',
});
await browser.close();
})();
O script executa as seguintes etapas:
printBackground
é importante para que as cores originais da página sejam preservadas 4. 게란도 또는 PDF
Agora que já temos nosso código funcionando e nosso script configurado conseguimos terminar nossas alterações para que o PDF possa ser gerado.
Como primeira etapa precisamos adicionar um novo parâmetro chamado generate
nos scripts do arquivo package.json
como no código abaixo.
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"generate": "npx babel-node src/generate.js"
},
Essa linha é necessária para que possamos usar o babel-node instalado para transpilar nosso código Javascript e executá-lo no Node.
Para gerar o PDF basta rodar o comando abaixo enquanto o servidor React estiver rodando:
yarn generate
O resultado da execução do script é a criação do arquivo PDF com o nome definido no script dentro da pasta assets. Perceba que o arquivo tem as mesmas características da página original e pode ser ampliado sem perder a qualidade, uma das vantagens de se utilizar esse método.
Parabéns! Agora você tem um PDF que representa perfeitamente sua página 😄
5. Pontos Importantes e dicas:
page.goto
pode necessitar da prop waiUntil
como no código abaixo. Para mais informações verifique a documentação oficial . await page.goto('http://localhost:3000/report-cba-full/12', { waitUntil: 'networkidle0' });
await page.setDefaultNavigationTimeout(0);
6. 코디고
O código utilizado nesse projeto está disponível no Github no repositório abaixo. Fique à vontade para experimentar variações e configurações. Porque não adicionar uma nova página ao seu PDF?
길헤르메데카스트롤라이트 / PDF 인형극
Puppeteer를 사용하여 PDF를 생성하기 위한 가이드의 샘플 컴패니언 리포지토리
Reference
이 문제에 관하여(Gerando PDFs utilizando React e Puppeteer), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/guilhermedecastroleite/gerando-pdfs-utilizando-react-e-puppeteer-37i5텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)