Creating PDFs with React

I'm a student, developer & guitarist! I love developing apps and useful tools that eases your life!
Table of Contents
- Tools & Packages
- Setting up the project
- Rendering React Components to HTML String
- Puppeteer
- Creating pdf
- Finishing the code
- Running the code
- Finished Code
Motivation
Creating PDFs using Puppeteer and some templating engine is rather simple and useful. Template engines like handlebars also support if else statements & custom functions defined by the developer but I wanted to use a different approach and have a more in-depth control over the data and use code splitting and have fun.
Tools & Packages
I'll be using
yarnas package manager in this article, if you're usingnpmcheck out this link
Setting up the project
With project-chef cli ( no configuration time )
If you're using project-chef cli you can follow the two quick steps and skip to Rendering React Components to HTML String
First run project-chef cli using
project-chefcommand then selectbackend > react-for-server-side
Then install puppeteer
yarn add puppeteer
Normal way to setup your project
First init a new nodejs project by creating a new folder and running
yarn initor
npm initInstall babel and other packages
yarn add react react-dom puppeteeryarn add -D @babel/cli @babel/core @babel/node @babel/plugin-proposal-class-properties @babel/plugin-transform-async-to-generator @babel/plugin-transform-runtime @babel/preset-env @babel/preset-react @babel/polyfillCreate
babel.config.jsonin the root of your project and paste the following{ "presets": ["@babel/preset-env", "@babel/preset-react"], "plugins": [ "@babel/plugin-transform-async-to-generator", "@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime", ] }
With that done, let's dive into coding!
Rendering React to HTML
We are only using react to create a simple html page to print it to pdf using puppeteer ( which I'll explain in the following section ), so we don't need to render the react components into a webpage.
Let's create a new folder under src named renderer
First creating our components in App.js in renderer
renderer/App.js
import React from "react";
const data = [
{ name: "Luke Skywalker", img: "https://upload.wikimedia.org/wikipedia/en/9/9b/Luke_Skywalker.png" },
{ name: "Obi-Wan (Ben) Kenobi", img: "https://upload.wikimedia.org/wikipedia/en/3/32/Ben_Kenobi.png" },
{ name: "Darth Vader", img: "https://upload.wikimedia.org/wikipedia/tr/8/83/DarthVader.JPG" },
];
function Character({ name, img }) {
return (
<div>
<img src={img} alt={name} height={250} />
<h3>{name}</h3>
</div>
);
}
function App() {
return (
<html>
<h1>Some Star Wars Characters</h1>
{data.map((char, i) => (
<Character {...char} key={i} />
))}
</html>
);
}
export default App;
Here we've created two basic react components and some data to show.
Then we create an index.js inside renderer
In renderer/index.js
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import App from "./App";
export function render() {
// rendering react component to html string
return renderToStaticMarkup(<App />);
}
This is the most important part, because we're not rendering the react component into an html page, instead of that we're rendering our react component into a html string
import { renderToStaticMarkup } from "react-dom/server";
Here we import the renderToStaticMarkup function from react-dom/server to render our component to html string.
renderToStaticMarkup(<App />);
In the render function we call the renderToStaticMarkup function and call our App component using JSX. (like we normally do in renderDOM)
Output of render function will be:
<html>
<h1>Some Star Wars Characters</h1>
<div>
<img
src="https://upload.wikimedia.org/wikipedia/en/9/9b/Luke_Skywalker.png"
alt="Luke Skywalker"
height="250"
/>
<h3>Luke Skywalker</h3>
</div>
<div>
<img
src="https://upload.wikimedia.org/wikipedia/en/3/32/Ben_Kenobi.png"
alt="Obi-Wan (Ben) Kenobi"
height="250"
/>
<h3>Obi-Wan (Ben) Kenobi</h3>
</div>
<div>
<img
src="https://upload.wikimedia.org/wikipedia/tr/8/83/DarthVader.JPG"
alt="Darth Vader"
height="250"
/>
<h3>Darth Vader</h3>
</div>
</html>
You might've been thinking where's the css of the page, that's an another topic I'll write about in my next article.
What is Puppeteer ?
Puppeteer is package that helps us control a headless chromium instance.
Creating PDFs using puppeteer
We know that puppeteer uses a chromium instance, so we just need to open our html page and print it to pdf like we are in a normal browser.
Create a createPdf.js in src:
import puppeteer from "puppeteer";
// Creates a pdf document from htmlContent and saves it to outputPath
export async function createPdf(outputPath, htmlContent) {
// launchs a puppeteer browser instance and opens a new page
const browser = await puppeteer.launch();
const page = await browser.newPage();
// sets the html of the page to htmlContent argument
await page.setContent(htmlContent);
// Prints the html page to pdf document and saves it to given outputPath
await page.emulateMediaType("print");
await page.pdf({ path: outputPath, format: "A4" });
// Closing the puppeteer browser instance
await browser.close();
}
The important points to catch here:
- We're setting the html content of the empty browser page, using the html string rendered by our react component
// sets the html of the page by htmlContent argument
await page.setContent(htmlContent);
- And we're calling the
page.pdfmethod to print the current browser page to pdf.
await page.pdf({ path: outputPath, format: "A4" });
Let's put everything together
Create an index.js in src:
import { render } from "./renderer";
import { createPdf } from "./createPdf";
async function main() {
// creating the html string using react
const html = render();
// printing page to pdf using puppeteer
await createPdf("./output.pdf", html);
}
main();
Here we've just called the two functions, we've declared in
- renderer/index.js - render function: Renders react to html string
- createPdf.js - createPdf function: Creates the pdf with given html
Running the code
- First add a
startscript inpackage.json{ // ... package.json "scripts": { "start": "babel-node src/index.js" } // ... package.json } - Then run the code with
yarn start - If no errors occurred, you should see an
output.pdfin the root of you project
What about styling the pdf then ???
I'll be writing about it in my next article! I'll put the link here when it is ready!
Finished Code
You can find the full code for this article here
I hope this article gave you a different approach creating pdf files using javascript