In this blog, I talk about a new personal project I've been working on: react-pdfmake-reconciler.
Background
I've recently been using pdfmake at work behind a React website. There are some PDF JavaScript libraries online and some even offer React interfacing like React PDF, but the reason the team have probably chosen to use pdfmake in particular was because of its table layout. pdfmake's table can repeat its header rows when it overflows to the next page and rows can be kept together. Although not as powerful and flexible as LaTeX for example, it's much easier to integrate with a JavaScript/TypeScript source code with a lower bar of entry.
Slight issues
Code organization
pdfmake's input is a declarative JavaScript object, (as opposed to command based like PostScript/Turtle.) Style is provided either inline or named. So, it is easy to think of extracting similar bits of code into functions or "components". But using these functions sometimes feels a bit less ergonomic than desired.
// Example pdfmake Content function
const getSection = (showContent: boolean): ContentStack => ({
stack: [
'Line 1',
showContent ? 'Line 2' : null,
'Line 3'
].filter(isNotNull)
})
const isNotNull = <T>(x: T | null): x is T =>
x !== null
The above function returns a ContentStack
, one of the Content
types of pdfmake. But Content
can also be its own array, i.e.
type Content =
| string
| number
| Content[]
| ContentStack
| OtherTypesOfContents
When using our content functions, we sometimes need to be knowledgeable of whether the return type is an array or not, because pdfmake sometimes treats them differently.
const contents: Content[] = [
getSingleContent(),
...getManyContents(),
]
And you can imagine what happens if a deeply nested function requires a parameter from the input call. That parameter would need to be passed down through every function call, as opposed to something like React Context.
Debugging process
pdfmake's PDF generation process works on the server side as well as the client side. For complex use cases, it is understandable developers can create a test live preview on the client side while tweaking logic and styles. The final visual of the PDF can be tested manually, but what if we have a more powerful way of checking out our content functions? (😉, 😉, React Developer Tools)
Developer familiarity
The content of our entire project is basically written in React JSX, except for this pdf generation pipeline. If writing document definitions of pdfmake was as similar to React as possible, or even directly inside React, then it could help new developers get into this side of the code base a lot quicker, with less impedance on writing nicely organized React code.
Adapting pdfmake to React
pdfmake is quite a popular JavaScript library for PDF generation. However, there didn't seem to be a popular React interface for it when I initially went looking for one. This is also when it piqued my interest to make one myself as an exercise on my weekend.
I immediately thought of these ways of solving the issue:
Solution 1: Walk the JSX tree
The simplest solution I thought of that didn't involve any special libraries or toolchain configuration: Write React components and serialize into pdfmake input by walking the ReactNode
tree and transforming each node.
Solution 2: Custom jsx factory
I've used Theme UI before and they have an optional jsx factory that allows a special sx={}
prop to style React components. This way, we can still write in JSX, but instead of them turning into ReactNode
, we can serialize them into the pdfmake input format.
It turns out someone had this idea a few years ago and has already written a package for this for pdfmake: JSX PDF
Solution 3: Writing a custom React Renderer
I've known react-reconciler for a while but haven't had time to try it out (probably from usage by React Three Fiber). This package is an official React package by Facebook, that allows you to write renderers similar to React Native/React DOM. This also means we can use pretty much all of React's capability if I use React Reconciler.
Final solution
I've decided in the end to use react-reconciler mostly because I wanted to take this opportunity to try and use it. But there are other reasons too. Since the result will be in actual React, we can use React features like Components, Hooks, Context, etc. without much consideration. React Reconciler can also inject into React Developer Tools, providing great dev time experience.
Using React Reconciler
There's a bit more setting up to do using React Reconciler, and since the package is still marked as experimental, there's missing docs here and there in the source code.
But this was also a great opportunity for me to learn more and re-familiarize myself with React. Although I have been using React for a few years now, I have not really delved into the mechanism of React and I only vaguely remember React's lifecycle. But I have subscribed to newsletters, so I sometimes read articles like this: https://v8.dev/blog/react-cliff, and how React Fiber uses monomorphism to optimize for modern JavaScript engines because of how they access JavaScript objects.
Over my weekend, I checked out react reconciler's source code and referenced against usages in other projects like react-art and react-three-fiber and finished the implementation using react-reconciler. I polished my code base after work over the next week.
In the end, I have created this package: react-pdfmake-reconciler. (Not a good name, but react-pdfmake was already taken). I have created a simple StackBlitz playground here: (Slight note: there's use of <iframe>
for your browser to preview the PDF. This doesn't work well on mobile.)
If you are interested in this project, check out these links:
Top comments (0)