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
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
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
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
todependencies
inpackage.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
- set
- Copy
.babelrc
file fromui-app
and place it into thepdf-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
// 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
import './dependencies.register.ts'
Create nestjs pdf module
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
{
"/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
"proxyConfig": "apps/ui-app/proxy.config.json",
"open": true
Now that we've gotten the setup done its time to write some code
Top comments (0)