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
yarn
as package manager in this article, if you're usingnpm
check 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-chef
command 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 init
or
npm init
Install babel and other packages
yarn add react react-dom puppeteer
yarn 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/polyfill
Create
babel.config.json
in 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.pdf
method 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
start
script 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.pdf
in 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