Anvil Engineering
Posted on August 13, 2021
Anvil's PDF generation API endpoint supports generating PDFs from HTML and CSS. For the sake of simplicity and interoperability, the endpoint only accepts vanilla HTML & CSS. Obviously writing vanilla HTML & CSS is painful and far out of fashion.
Modern technologies like React, Vue, LESS, SASS, styled-components, etc. allow you to write more modular, reusable code. Fortunately these technologies all compile down to vanilla HTML and CSS.
So, while the endpoint accepts only vanilla HTML and CSS, you can use any technologies you'd like to generate the HTML and CSS then send it to the API. For example, you can use the aforementioned libraries like React and Vue, or even your crazy homegrown template language. As long as a library can generate HTML and CSS strings, you can use it with the API!
In this post, I'll show you how to use React and styled-components in Node to create several PDFs, then ultimately an invoice PDF.
The gist of the post is: use the React and styled-components libraries to generate vanilla HTML + CSS strings, then send those strings to the API. Let's get started.
Set up the scaffolding
The first thing we'll do is set up a quick node script to generate a PDF from plain HTML and CSS. We'll use vanilla HTML and CSS strings at first, then we'll incrementally layer in React and styled-components.
First thing, make sure you have an API key, then install the Anvil Node API client:
yarn add '@anvilco/anvil'
# or
npm install '@anvilco/anvil'
Then this script will generate the PDF:
// generate-pdf.js script
import fs from 'fs'
import path from 'path'
import Anvil from '@anvilco/anvil'
const apiKey = 'YOUR_ANVIL_API_KEY'
// Subsequent code samples will focus on modifying
// this buildHTMLToPDFPayload() function
function buildHTMLToPDFPayload () {
// We'll change these lines soon!
const html = '<div>Hello World</div>'
const css = ''
return {
data: {
html,
css,
},
}
}
async function main () {
const client = new Anvil({ apiKey })
const exampleData = buildHTMLToPDFPayload()
const { statusCode, data, errors } = await client.generatePDF(exampleData)
if (statusCode === 200) {
fs.writeFileSync('output.pdf', data, { encoding: null })
} else {
console.log(statusCode, JSON.stringify(errors || data, null, 2))
}
}
main()
Run this script, and you'll see the following output. Great job!
Add babel
Using React jsx
syntax in node requires the use of babel
. I assume most readers will have babel
setup already. If you do, skip this section!
What follows is a super minimal install to get you up and running. Your production environment will likely be much more robust. The first step is installing two core packages, then a couple of presets—the most important being @babel/preset-react
.
yarn add -D @babel/core @babel/node @babel/preset-env @babel/preset-react
# or
npm install --save-dev @babel/core @babel/node @babel/preset-env @babel/preset-react
The last step is adding a .babelrc
file to the root of your project that uses the installed presets:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
]
}
Now you can run the script with yarn babel-node generate-pdf.js
.
Add React
Time to generate some HTML with React. We'll be using server-side rendering via react-dom/server
.
Install react
and react-dom
yarn add react react-dom
# or
npm install react react-dom
Now we use ReactDOMServer.renderToStaticMarkup
to generate the HTML. We're using renderToStaticMarkup
instead of renderToString
because renderToStaticMarkup
omits some attributes React uses for dynamic updates. Since we're generating a PDF, there are no dynamic updates, so we have no use for the extra attributes.
// Import React packages
import React from 'react'
import ReactDOMServer from 'react-dom/server'
// The component to render
const Hello = () => (
<div>Hello React!</div>
)
function buildHTMLToPDFPayload () {
// Then generate an HTML string!
const html = ReactDOMServer.renderToStaticMarkup(
<Hello />
)
// css is still the same for now...
const css = ''
return {
data: {
html,
css,
},
}
}
Run the script and you're cooking with gas (React):
Add styled-components
Next up is the CSS. We will generate the CSS string with styled-components
. First install styled-components
:
yarn add styled-components
# or
npm install styled-components
Like React, styled-components
has server rendering abilities. Our path forward will be to create a new ServerStyleSheet
, render sheet.collectStyles(<YourComponent />)
, then get all the styles as a string.
A quick snippet shows how it interacts with React.
import { ServerStyleSheet } from 'styled-components'
// Generate the HTML, taking care to render the
// component with styled-components
const sheet = new ServerStyleSheet()
const html = ReactDOMServer.renderToStaticMarkup(
sheet.collectStyles(
<Hello />
)
)
const css = sheet.instance.toString()
Then, here's that snippet in context with some actual styling:
// Import styled-components
import styled, { ServerStyleSheet } from 'styled-components'
// Use styled components
const Container = styled.div`
font-size: 20px;
`
const Magenta = styled.span`
color: magenta;
`
const Blue = styled.span`
color: blue;
`
const Hello = () => (
<Container>
Ooh, <Magenta>so</Magenta> <Blue>pretty</Blue>!
</Container>
)
function buildHTMLToPDFPayload () {
// Generate the HTML, taking care to render the
// component with styled-components
const sheet = new ServerStyleSheet()
const html = ReactDOMServer.renderToStaticMarkup(
sheet.collectStyles(
<Hello />
)
)
// Finally, get the CSS as a string
const css = sheet.instance.toString()
return {
data: {
html,
css,
},
}
}
The result:
React & styled components to HTML & CSS to PDF
Insert global styles
You will likely need to inject a few global styles into the PDF. For example, you may want to set the font size, color, page details, etc. You can leverage styled-components’ createGlobalStyle()
function. In the last example, we set the Container
's font-size to 20px
, but we could just as well do that styling in the body
rule.
Here's a simplified snippet:
import styled, { ServerStyleSheet, createGlobalStyle } from 'styled-components'
const GlobalStyle = createGlobalStyle`
body {
font-size: 20px;
}
`
const Hello = () => ( /* render hello */ )
const sheet = new ServerStyleSheet()
const html = ReactDOMServer.renderToStaticMarkup(
sheet.collectStyles(
<>
<GlobalStyle />
<Hello />
</>
)
)
const css = sheet.instance.toString()
Using this in our script gives us the same result as the last example:
Same result with global styles
The whole script
Here we are with the whole enchilada: React, styled-components
, and global styles. This script contains everything you need to render whatever you want to PDF by way of React and styled-components.
import fs from 'fs'
import path from 'path'
import Anvil from '@anvilco/anvil'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import styled, { ServerStyleSheet, createGlobalStyle } from 'styled-components'
const apiKey = 'YOUR_ANVIL_API_KEY'
const GlobalStyle = createGlobalStyle`
body {
font-size: 20px;
}
`
const Package = styled.span`
color: magenta;
`
const Hello = () => (
<div>
Hello from <Package>React</Package> & <Package>styled-components</Package>!
</div>
)
function buildHTMLToPDFPayload () {
const sheet = new ServerStyleSheet()
const html = ReactDOMServer.renderToStaticMarkup(
sheet.collectStyles(
<Hello />
)
)
const css = sheet.instance.toString()
return {
data: {
html,
css,
},
}
}
async function main () {
const client = new Anvil({ apiKey })
const exampleData = buildHTMLToPDFPayload()
const { statusCode, data, errors } = await client.generatePDF(exampleData)
if (statusCode === 200) {
fs.writeFileSync('output.pdf', data, { encoding: null })
} else {
console.log(statusCode, JSON.stringify(errors || data, null, 2))
}
}
main()
An invoice example
Hello-world examples are cute and all, but you may want to see a real-life example.
We've created React to PDF invoice example using the approach in this blog post. Along with the tech discussed in this post, the invoice example uses advanced HTML & CSS features like page numbering, header / footer rendering, special table features, etc.
Invoice PDF generation example using React & styled-components
Summary
You should have all the tools necessary to create PDFs from your own React & styled-components code. You can use other technologies of your choice (e.g. Vue + SCSS) by extrapolating the approach in this post. That is, if it can output HTML and CSS strings, you can use it with the HTML to PDF endpoint.
If you have questions or you're developing something cool with PDFs, let us know at developers@useanvil.com. We’d love to hear from you.
Posted on August 13, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.