React and Puppeteer: Pdf generation (project setup)
Tags: pdf, react, puppeteer, nx
Published: 2022-10-02 13:42:29

## Summary

This is an experimentation detailing how to generate pdf documents using a server and React as the template engine.

## Why
I have used `@react-pdf/renderer` in the past and this aims to solve a few issues I had with it

### Issues
- Limited set of components to work with
- Maintenance has slowed down, therefore, the future outlook doesnt look bright for unresolved issues
- Currently the library does not work with react 18 so this makes it difficult to upgrade an application to react 18

### Goals
- Decouple pdf generation from the client application
- Have a simple server process to generate PDF documents
- Keep using React as a familiar library for UI development
- Only critical coding steps will be detailed, please check the final source code if something is missing in the steps

### Why not just do it in the user's browser/client?
- This is meant for use cases where there needs to be a repeatable process
- In this scenario, simply creating print styles doesnt work as the view needed for the pdf is not necessarily what is being viewed in the client
- user could be using a mobile application

**_Note:_** The goals, libraries and specific data flows used for the experiment are purely subjective as not every use case will need to do all these steps. Mainly focus on how the pdf is generated.

### Technology
- **React:** use as a template engine. any other templating engine can be used
  - **chakra ui:** used as a ui library. not needed but has a lot of simple to use components for a demo. alternatives ca be used
  - **storybook:** used as a ui development assistant library. not needed but good to use to develop the view/pdf visually
- **Puppeteer:** used as an in place html renderer and orchestrator: alternatives can also be used
- **nx:** used for project management. We will create multiple projects in the same repository. Nx makes this easier to link up all projects. Alternatives can be used

## TL;DR;
view the full project on github:
https://github.com/lwhiteley/pdf-generation-experiment

```bash
git clone https://github.com/lwhiteley/pdf-generation-experiment
cd pdf-generation-experiment
pnpm i
pnpm nx run-many --target=serve --projects=pdf-server,ui-app
```

After running these commands, you can then use the app  experience the full solution.

------

## Setup

### Create project + Install dependencies 

```bash
npx create-nx-workspace --pm pnpm pdf-generation
# ? Enable distributed caching to make your CI faste: No

cd pdf-generation
pnpm add -D @nrwl/react @nrwl/nest @nrwl/storybook @nrwl/node
pnpm add pdf-lib puppeteer utf-8-validate sharp
pnpm add @chakra-ui/react @emotion/react @emotion/styled framer-motion @chakra-ui/icons @chakra-ui/styled-system use-debounce axios
pnpm add -D @chakra-ui/storybook-addon
pnpm add interweave interweave-ssr

```

#### Generate the projects we will work with

```bash
pnpm nx g @nrwl/react:application ui-app
  # Choose: emotion
  # use router: Yes

pnpm nx g @nrwl/nest:application pdf-server

pnpm nx g @nrwl/node:library constants

pnpm nx g @nrwl/react:library pdf-doc

pnpm nx g @nrwl/storybook:configuration pdf-doc
  # Choose: @storybook/react 
```
Notes:
- Ensure to add the chakra ui storybook plugin 
- docs: https://chakra-ui.com/getting-started/with-storybook

### Configure pdf-server for React and deployment

- Move `@babel/core` to `dependencies` in `package.json`
- in `apps/pdf-server/project.json`
  - set `targets.build.options.generatePackageJson: true`
  - essentially adding: `"generatePackageJson": true` in the specified location of the json file
- Copy `.babelrc` file from `ui-app` and place it into the `pdf-server` root folder
- Add `"jsx": "react-jsx"` in 
   - apps/pdf-server/tsconfig.app.json
   - apps/pdf-server/tsconfig.spec.json

Create a file at the same level of `apps/pdf-server/src/main.ts`

```ts
// file: dependencies.register.ts

/**
 * We import dependencies that are missed by nx auto package.json creation
 */

import '@babel/core';
import '@emotion/styled';
import '@chakra-ui/styled-system';
import 'utf-8-validate';
import { polyfill } from 'interweave-ssr';

// view this file for environment config
import { environment } from './environments/environment';
 
polyfill();

// this could be moved to a helper library
export const createDirectory = (directory: string) => {
  if (existsSync(directory)) return false;
  mkdirSync(directory, { recursive: true });
  return true;
};

createDirectory(environment.tmpFolder);

```

Then import it in `main.ts`

```ts
import './dependencies.register.ts'
```

### Create nestjs pdf module

```bash
pnpm nx g @nrwl/nest:module pdf --project=pdf-server
pnpm nx g @nrwl/nest:controller pdf --project=pdf-server
pnpm nx g @nrwl/nest:service pdf --project=pdf-server
```

### Proxy the `pdf-server` to `ui-app`

Create a file `apps/ui-app/proxy.config.json`

```json
{
  "/api": {
    "target": "http://localhost:3333/api",
    "pathRewrite": { "^/api": "" },
    "secure": false,
    "logLevel": "debug"
  }
}
```

then in `apps/ui-app/project.json`

add the following to `targets.serve.configurations.development`

```json
"proxyConfig": "apps/ui-app/proxy.config.json",
"open": true
```

--------

Now that we've gotten the setup done its time to write some code