diegomura / react-pdf

📄 Create PDF files using React
https://react-pdf.org
MIT License
14.22k stars 1.11k forks source link

renderToStream: NodeJS.ReadableStream will not complete, end event will not fire. #2706

Open DaveFPath opened 1 month ago

DaveFPath commented 1 month ago

Describe the bug I am trying to create a pdf file on my backend and then serve it to my front end for download. Since renderToString has been deprecated, I am forced to use renderToStream. Then convert the stream to a buffer to send back in the response. I've tried sending the stream back directly in the response object but it throws an error.

export const renderPDF = async () => {

  // all the code that builds the pdf guts. "PDFComponent" is a react component that actually makes the pdf dom.
  const pdfGuts = { ... };

  const rootElemComponent: any = React.createElement(PDFComponent, pdfGuts);
  return ReactPDF.renderToStream(rootElemComponent);
};

export const renderToBuffer = (stream: NodeJS.ReadableStream) => {
  return new Promise<Buffer>(function (resolve, reject) {
    const chunks: any[] = [];
    stream.on("data", function (chunk: any) {
      return chunks.push(chunk);
    });
    stream.on("end", function () {
      return resolve(buffer.Buffer.concat(chunks));
    });
    stream.on("error", function (error: any) {
      return reject(error);
    });
  });
};

@Get(":id/billOfLading")
@HttpCode(HttpStatus.OK)
@Header("Content-Type", "application/pdf")
@Header("Response-Type", "arraybuffer")
@Header("Response-Encoding", "binary")
async getDocument(@CurrentUser() user, @Param("id") id, @Res() res) {

  const data: NodeJS.ReadableStream = await renderPDF();

  // convert the stream to an array bufferl
  const buffer = await renderToBuffer(data);  // <-- Boom! never returns anything... just freezes.

  // send the document to the client.
  res.send(buffer);
}

in my renderToBuffer function, the "data" event triggers just fine for each chunk, however, when it's finished, the "end" event never fires. Nor does the "finish" event.

I have tried several different ways of reading the stream, I have tried renderToFile as well and it too freezes up. (I'm assuming it's the same problem internally when writing the file, reading the stream never triggers and end event.)

Any thoughts?

To Reproduce See code above.

Expected behavior I expect that the readable stream will actually finish when all the data has been read.

Desktop (please complete the following information):

DaveFPath commented 1 month ago

Update: I added a simple timer to the data event so that after 500 milliseconds if no data has come in, it manually emits the end event. This does trigger the event event and my code continues to run... however, the pdf that is output is unreadable as a pdf.

I also think that something under the hood is still operating and waiting because there is a memory leak when I try to execute the function multiple (a lot) times.

DaveFPath commented 1 month ago

Found the exact source of the problem. It had to do with loading a font family from a CDN. Here's a breakdown of what was going on:

In my document component, I would have the following

Font.register({
  family: "OpenSans",
  fonts: [
    {
      src: "https://cdn.jsdelivr.net/npm/@typopro/web-open-sans@3.7.5/TypoPRO-OpenSans-Regular.ttf",
    },
    {
      src: "https://cdn.jsdelivr.net/npm/@typopro/web-open-sans@3.7.5/TypoPRO-OpenSans-Bold.ttf",
      fontWeight: "bold",
    },
  ],
});

const styles = StyleSheet.create({
  page: {
    height: "100%",
    display: "none",
    backgroundColor: "white",
    paddingTop: 20,
    paddingBottom: 30,
    paddingHorizontal: 10,
    fontFamily: "OpenSans",
    fontSize: 8,
  },
});

export const BillOfLading: React.FC<BillOfLadingProps> = props => {

  ... 

  return (
    <Document>
        <Page size="A4" style={styles.page}>
          <Text>Test #14</Text>
        </Page>
    </Document>
}

Now, when I run the above, the document will not generate. If I simply remove the style for the fontFamily, everything works just fine.

My solution was to remove the OpenSans font family all together and just use the default Helvetica

I hope this helps find the source of the problem.

Katli95 commented 1 month ago

I'm running into the same, loading fonts from local, but still, using non-standard fonts.

Katli95 commented 1 month ago

I can confirm in my case it's a duplicate of #2675 which is primarily worded as a front-end issue, but npm i restructure@3.0.0 stopped me from running into the issue. I'd suggest to close this as a duplicate and add it to the description of the other issue.