question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support non-blocking rendering (Node/Web Workers)

See original GitHub issue

Describe the bug React PDF rendering hogs the main thread on a Hapi web server, blocking other operations, e.g. HTTP responses and resulting in HTTP 500/503 errors

To Reproduce Render a document to PDF by streaming ReactPDF.renderToStream to Node’s HTTP response (which takes >5000ms) while also trying to handle lots of normal incoming HTTP requests 🤣 (I can provide a reproducible sample if needed)

Expected behavior Ideally, ReactPDF.renderToStream would optionally return a Promise resolving to a stream that didn’t block the main thread. This could be by delegating the actual PDF creation to another thread, like in a Worker thread in Node or a Web Worker in the browser. Although Node seems a higher priority right now, it seems it would be relatively trivial to code for both. We could then stream the PDF back to the calling thread.

I have started working on a solution along these lines but wanted to see if this sounds like a good approach to take. Let me know what you think @diegomura and other contributors.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:11
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

25reactions
kidrocacommented, Jul 29, 2021

I’ve successfully used a web worker that uses react-pdf/render to generate a multiple PDF documents in a batch

Here’s how that looks like

web.worker.js

import * as Comlink from 'comlink';
import { pdf } from '@react-pdf/renderer';
import JSZip from 'jszip';

/* This prevents live reload problems during development
* https://stackoverflow.com/questions/66472945/referenceerror-refreshreg-is-not-defined */
if (process.env.NODE_ENV != 'production') {
  global.$RefreshReg$ = () => {};
  global.$RefreshSig$ = () => () => {};
}

let progressCb = console.info;

const generateAll = async (patients = []) => {
  const archiveBuilder = new JSZip();

  progressCb({ progress: 1, current: 0, total: patients.length });

  for (let i = 0; i < patients.length; i++) {
    const patient = patients[i];

    const { fileName, pdfBlob } = await generateSingle(patient);
    archiveBuilder.file(fileName, pdfBlob);

    const current = i + 1;
    const progress = (current / patients.length) * 100;
    progressCb({ progress, current, total: patients.length });
  }

  const zipBlob = await archiveBuilder.generateAsync({ type: 'blob' });
  return zipBlob;
};

const generateSingle = async (patient) => {
  // This is a react-pdf/renderer document: <Document><Page>...</Page></Document>
  const { BillingReportDocument } = require('../../../pdf/PdfBillingReport');

  const document = <BillingReportDocument patient={patient} />;
  const { id, data } = patient;
  const fileName = `Report {id}.pdf`;

  const pdfBuilder = pdf(document);
  const pdfBlob = await pdfBuilder.toBlob();
  return { pdfBlob, fileName };
};

const onProgress = cb => (progressCb = cb);

Comlink.expose({
  generateAll,
  generateSingle,
  onProgress,
});

ExportReports.js

import * as Comlink from 'comlink';
import { saveAs } from 'file-saver';
import { useAsyncCallback } from 'react-async-hook';
/* eslint-disable import/no-webpack-loader-syntax */
import Worker from 'worker-loader!./web.worker.js';

import ActionButton from '../../../../components/ActionButton';

const ExportReports = ({ month, children, ...rest }) => {
  const exportAction = useExportCallback(month);

  return (
    <ActionButton
      status={exportAction.status}
      onClick={exportAction.execute}
      {...rest}
    >
      {exportAction.loading ? 'Exporting...' : children}
    </ActionButton>
  );
};

const useExportCallback = (month) => useAsyncCallback(async () => {
  const data = await fetchData({ month });

  const worker = new Worker();
  const pdfWorker = Comlink.wrap(worker);
  pdfWorker.onProgress(Comlink.proxy(info => console.log(info));

  const zipBlob = pdfWorker.generateAll(data.patients);
  saveAs(zipBlob, 'Report.zip');
});

export default ExportReports;

If you want to preview the PDF you can use URL.createObjectURL(zipBlob) and use the created url in an iframe

It should be possible to have this implemented in react-pdf/renderer, but in the meantime this can serve anyone who deals with large PDFs that cause UI freezes

5reactions
pengw00commented, Jan 18, 2021

Hello, is it this non-blocking rendering is resolved?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Web Workers Explained with Rendering Performance - Medium
The article uses browser page rendering performance to demonstrate the use of Web Workers. Hopefully you will find most of it useful and...
Read more >
Using Web Workers - Web APIs - MDN Web Docs
Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering ......
Read more >
Supporting Web Workers API in Node.js vs Just Using Deno
Keeping the main thread responsive to prevent incoming requests from being blocked. At it's core, Worker Threads functionality was designed to ...
Read more >
Multithreading in JavaScript with Web Workers - LeanyLabs
Unlike asynchronous code, where each callback still waits for the opportunity to run in the main thread, multiple modules are executed together in...
Read more >
Web Workers: For non-blocking User Interface
In this post, I'll explain how you can use web workers to perform CPU intensive tasks without blockin... Tagged with javascript, webperf.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found