wojtekmaj / react-pdf

Display PDFs in your React app as easily as if they were images.
https://projects.wojtekmaj.pl/react-pdf
MIT License
9.5k stars 891 forks source link

Next.js and v9 on build Promise.withResolvers #1811

Closed Kasui92 closed 5 months ago

Kasui92 commented 5 months ago

Before you start - checklist

Description

I'm trying to implement the library in a Next.js 14.2.3 (App Router) project without turbopack, however when I go to use the proposed examples it reports the error in the title directly in the console during development or when it's launched the build command.

Having to use Node 20 because Vercel doesn't support 22, I immediately adopted legacy mode to solve the problem... but with poor results.

I also installed core-js and imported directly into the layout in root, but although it solves the error during development, it gives me an error when I build the app.

Can I ask if I'm missing any steps?

Steps to reproduce

  1. Install Next.js App
  2. Install React-Pdf v9
  3. Install core-js
  4. Use the files in information paragragh
  5. Try to build

Expected behavior

The build command should work.

Actual behavior

The build command reports an error "failed to parse input file" or "Promise.withResolvers" if core-js is not installed.

Additional information

/src/app/layout.js

import 'core-js/full/promise/with-resolvers'

export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <body>
                <main >{children}</main>
            </body>
        </html>
    )
}

/src/app/page.js

import PdfViewer from './_components/PdfViewer'

export default async function ReactPDF() {
    return (
        <div>
                    <PdfViewer />
        </div>
    )
}

/src/app/PdfViewer.js

'use client'

import { useCallback, useState } from 'react'
import { pdfjs, Document, Page } from 'react-pdf'
import 'react-pdf/dist/esm/Page/AnnotationLayer.css'
import 'react-pdf/dist/esm/Page/TextLayer.css'

pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/legacy/build/pdf.worker.min.mjs', import.meta.url).toString()

const options = {
    cMapUrl: '/cmaps/',
    standardFontDataUrl: '/standard_fonts/',
}

const maxWidth = 800

export default function PdfViewer() {
    const [file, setFile] = useState('./sample.pdf')
    const [numPages, setNumPages] = useState()
    const [containerRef, setContainerRef] = useState(null)
    const [containerWidth, setContainerWidth] = useState()

    const onResize = useCallback((entries) => {
        const [entry] = entries

        if (entry) {
            setContainerWidth(entry.contentRect.width)
        }
    }, [])

    function onFileChange(event) {
        const { files } = event.target

        const nextFile = files?.[0]

        if (nextFile) {
            setFile(nextFile)
        }
    }

    function onDocumentLoadSuccess({ numPages: nextNumPages }) {
        setNumPages(nextNumPages)
    }

    return (
        <div className="Example">
            <div className="Example__container">
                <div className="Example__container__document" ref={setContainerRef}>
                    <Document file={file} onLoadSuccess={onDocumentLoadSuccess} options={options}>
                        {Array.from(new Array(numPages), (el, index) => (
                            <Page
                                key={`page_${index + 1}`}
                                pageNumber={index + 1}
                                width={containerWidth ? Math.min(containerWidth, maxWidth) : maxWidth}
                            />
                        ))}
                    </Document>
                </div>
            </div>
        </div>
    )
}

The example PDF is the same one used in the sample, placed in /public.

Environment

lucaslosi commented 5 months ago

downgrading to 8.0.2 solved the issue

craigcartmell commented 5 months ago

Also seeing this issue on Next 14 during the build process.

8.0.2 has a high security vulnerability due to referencing pdfjs-dist@3.11.174, so it's not a great workaround.

allicanseenow commented 5 months ago

Also experiencing the same issue.

vpsk commented 5 months ago

I was able to resolve the issue by setting the dynamic component to load without server-side rendering (SSR). const PdfViewerComponent = dynamic(() => import("./PdfViewer"), { ssr: false, });

and make sure in next.config.js enable this config.resolve.alias.canvas = false;

craigcartmell commented 5 months ago

I was able to resolve the issue by setting the dynamic component to load without server-side rendering (SSR). const PdfViewerComponent = dynamic(() => import("./PdfViewer"), { ssr: false, });

and make sure in next.config.js enable this config.resolve.alias.canvas = false;

Which version of Next/React-PDF are you using?

Would you be able to paste your full component please?

wojtekmaj commented 5 months ago

All, please kindly see the updated samples:

and see updated upgrade guide for workarounds:

craigcartmell commented 5 months ago

@wojtekmaj - I think the upgrade guide should read: experimental.esmExternals: 'loose', not experiments.

NikkiHess commented 5 months ago

Regarding the workarounds... I'm unsure how to use the polyfill for Promise.withResolvers. Is there a guide somewhere on how to do this for react-pdf?

justinfarrelldev commented 5 months ago

I'm also experiencing this on Remix v2.9.1. Trying to polyfill Promise.withResolvers now

Edit: It seems that the error is coming from within the worker itself, making this difficult to polyfill. I have tried to place a polyfill both in the root as well as in the client component where I have included the worker, but neither worked for me.

wojtekmaj commented 5 months ago

Perhaps using a legacy worker would help? πŸ€”

justinfarrelldev commented 5 months ago

Perhaps using a legacy worker would help? πŸ€”

I tried using a legacy worker, but sadly that did not help. That being said, the following polyfill worked like a charm:

// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === 'undefined') {
    if (window)
        // @ts-expect-error This does not exist outside of polyfill which this is doing
        window.Promise.withResolvers = function () {
            let resolve, reject;
            const promise = new Promise((res, rej) => {
                resolve = res;
                reject = rej;
            });
            return { promise, resolve, reject };
        };
}

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url
).toString();

Once I added this, it worked fantastically! 😁

fate7bardiche commented 5 months ago

I referred to this commit and used a polyfill from core-js. Commit that added the polyfill

As a result, I was able to make it work.

If core-js is available, this method might also work.

justinwaite commented 5 months ago

Perhaps using a legacy worker would help? πŸ€”

I tried using a legacy worker, but sadly that did not help. That being said, the following polyfill worked like a charm:

// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === 'undefined') {
    if (window)
        // @ts-expect-error This does not exist outside of polyfill which this is doing
        window.Promise.withResolvers = function () {
            let resolve, reject;
            const promise = new Promise((res, rej) => {
                resolve = res;
                reject = rej;
            });
            return { promise, resolve, reject };
        };
}

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url
).toString();

Once I added this, it worked fantastically! 😁

@justinfarrelldev Where did you add this specifically? I assumed adding to entry.client would be the place, but that didn't resolve it for me.

justinfarrelldev commented 4 months ago

Perhaps using a legacy worker would help? πŸ€”

I tried using a legacy worker, but sadly that did not help. That being said, the following polyfill worked like a charm:

// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === 'undefined') {
    if (window)
        // @ts-expect-error This does not exist outside of polyfill which this is doing
        window.Promise.withResolvers = function () {
            let resolve, reject;
            const promise = new Promise((res, rej) => {
                resolve = res;
                reject = rej;
            });
            return { promise, resolve, reject };
        };
}

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url
).toString();

Once I added this, it worked fantastically! 😁

@justinfarrelldev Where did you add this specifically? I assumed adding to entry.client would be the place, but that didn't resolve it for me.

Sorry, I could have been more clear - I put this snippet in the component where the Document and Page tags are (IE, the component used to show the PDF - I called it pdfViewer.client.tsx).

For more context, I also imported the TextLayer.css and AnnotationLayer.css within this file (at the top, as imports) as well as the worker src.

Basically, almost all react-pdf logic is isolated within this client component. I'll post the source code shortly (and edit this comment when I do).

Edit: Here's the source that works for me (this file is pdfViewer.client.tsx). Yes, my JSDoc is badly out of date (it was generated with Hygen boilerplate and I haven't updated it). Note that the memoization is to prevent re-renders of the PDF Viewer (causing it to load the PDF again):

/*
    Description: The viewer for pdfs. Needs a buffer to render
*/
import React, { FC, useMemo } from 'react';
import { pdfjs, Document, Page } from 'react-pdf';
import 'react-pdf/dist/Page/TextLayer.css';
import 'react-pdf/dist/Page/AnnotationLayer.css';

// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === 'undefined') {
    if (window)
        // @ts-expect-error This does not exist outside of polyfill which this is doing
        window.Promise.withResolvers = function () {
            let resolve, reject;
            const promise = new Promise((res, rej) => {
                resolve = res;
                reject = rej;
            });
            return { promise, resolve, reject };
        };
}

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url
).toString();

type Props = {
    buffer: { data: Buffer };
};

/**
 * The viewer for pdfs. Needs a buffer to render
 *
 * @param {Props} param0
 * @param {string} param0.children The children of this component.
 * @returns {React.ReactElement}
 */
export const PdfViewer: FC<Props> = ({ buffer }: Props): React.ReactElement => {
    const memoizedFile = useMemo(() => buffer.data, [buffer.data]); // Depend on buffer.data assuming buffer.data is stable and only changes if actual data changes

    // Memoize the file object to prevent unnecessary re-renders
    const fileProp = useMemo(() => ({ data: memoizedFile }), [memoizedFile]);

    return (
        <div>
            <Document
                file={fileProp}
                onLoadError={(err) =>
                    console.error(`Loading error from PDF viewer: ${err}`)
                }
                onLoadStart={() => console.log('Started loading pdf')}
                onLoadSuccess={(pdf) =>
                    console.log('Successfully loaded pdf:', pdf)
                }
            >
                <Page pageIndex={0} />
            </Document>
        </div>
    );
};
adrianbienias commented 4 months ago

The issue is still present.

After running the example app https://github.com/wojtekmaj/react-pdf/tree/main/sample/next-app

I'm getting error in the console TypeError: Promise.withResolvers is not a function

Using the proposed polyfills doesn't seem to solve it.

justinfarrelldev commented 4 months ago

The issue is still present.

After running the example app https://github.com/wojtekmaj/react-pdf/tree/main/sample/next-app

I'm getting error in the console TypeError: Promise.withResolvers is not a function

Using the proposed polyfills doesn't seem to solve it.

Try adding an "else" to the polyfill I posted above and then using "global" instead of window within that else statement. That should help to handle SSR

adrianbienias commented 4 months ago
if (typeof Promise.withResolvers === "undefined") {
  if (window) {
    // @ts-expect-error This does not exist outside of polyfill which this is doing
    window.Promise.withResolvers = function () {
      let resolve, reject
      const promise = new Promise((res, rej) => {
        resolve = res
        reject = rej
      })
      return { promise, resolve, reject }
    }
  } else {
    // @ts-expect-error This does not exist outside of polyfill which this is doing
    global.Promise.withResolvers = function () {
      let resolve, reject
      const promise = new Promise((res, rej) => {
        resolve = res
        reject = rej
      })
      return { promise, resolve, reject }
    }
  }
}

Nothing changes, the same error occurs.

I also tried https://github.com/wojtekmaj/react-pdf/commit/2ba89d8cb968af6e522e688329cbf2e412b80462 with the same result

maxess3 commented 4 months ago

I referred to this commit and used a polyfill from core-js. Commit that added the polyfill

As a result, I was able to make it work.

If core-js is available, this method might also work.

  • Node.js 19.7.0
  • "core-js": "^3.37.1"
  • "react-pdf": "^9.0.0"

Thank you, It works for me with the same configuration.

siinghd commented 4 months ago

Any update on this?, none of above worked for me

Ori2846 commented 4 months ago

Loading a Polyfill and putting import './polyfills.mjs'; at the top of my next.config.mjs worked for me. This made Promise.withResolvers available in both browser and server environments.

Here's the code I used:


import 'core-js/full/promise/with-resolvers.js';

// Polyfill for environments where window is not available (e.g., server-side rendering)
if (typeof Promise.withResolvers === 'undefined') {
  if (typeof window !== 'undefined') {
    window.Promise.withResolvers = function () {
      let resolve, reject;
      const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });
      return { promise, resolve, reject };
    };
  } else {
    global.Promise.withResolvers = function () {
      let resolve, reject;
      const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });
      return { promise, resolve, reject };
    };
  }
}
DonikaV commented 4 months ago

Updated to latest one 9.1.0 and now getting this error, downgraded and still same :| Basically every time when I update version I getting some errors...

ldiqual commented 4 months ago

Super hacky, but this is how we added the polyfill to the pdfjs worker itself, in vite.config.ts:

function transformPdfJsWorker(): Plugin {
  return {
    name: 'transform-pdf-js-worker',
    generateBundle(options, bundle) {
      for (const [fileName, chunkOrAsset] of Object.entries(bundle)) {
        if (!fileName.includes('pdf.worker') || chunkOrAsset.type !== 'asset') {
          continue
        }
        const prepend = Buffer.from(
          `if (typeof Promise.withResolvers === "undefined") {
            Promise.withResolvers = function () {
              let resolve, reject
              const promise = new Promise((res, rej) => {
                resolve = res
                reject = rej
              })
              return { promise, resolve, reject }
            }
          }
          `,
          'utf-8'
        )
        const sourceBuffer = Buffer.isBuffer(chunkOrAsset.source)
          ? chunkOrAsset.source
          : Buffer.from(chunkOrAsset.source)
        chunkOrAsset.source = Buffer.concat([prepend, sourceBuffer])
      }
    },
  }
}

export default defineConfig({
  plugins: [
    transformPdfJsWorker(),
  ],
})
allicanseenow commented 4 months ago
if (typeof Promise.withResolvers === "undefined") {
  if (window) {
    // @ts-expect-error This does not exist outside of polyfill which this is doing
    window.Promise.withResolvers = function () {
      let resolve, reject
      const promise = new Promise((res, rej) => {
        resolve = res
        reject = rej
      })
      return { promise, resolve, reject }
    }
  } else {
    // @ts-expect-error This does not exist outside of polyfill which this is doing
    global.Promise.withResolvers = function () {
      let resolve, reject
      const promise = new Promise((res, rej) => {
        resolve = res
        reject = rej
      })
      return { promise, resolve, reject }
    }
  }
}

Nothing changes, the same error occurs.

I also tried 2ba89d8 with the same result

This works for me if I put the polyfill in _app.tsx. It works in both local and production.

gabsoftware commented 4 months ago

For anyone still struggling with this. Don't bother loading polyfills yourself, it will not work.

You have to load the Legacy version of PdjJS worker which is already polyfilled. See https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions#faq-support

Like so:

import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url,
).toString();
gabsoftware commented 4 months ago

Okay, so after a few tests, you still have to polyfill Promise.withResolvers because react-pdf uses non-legacy pdf.mjs even if you choose the legacy pdf worker. So legacy pdf worker + polyfilled Promise.withResolvers + react-pdf works as intended.

aldwnesx commented 4 months ago

Getting the same error:

 β—‹ Compiling / ...

warn - No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.
warn - https://tailwindcss.com/docs/content-configuration
 βœ“ Compiled / in 673ms (567 modules)
 β¨― TypeError: Promise.withResolvers is not a function
    at __webpack_require__ (/Users/al/Documents/GitHub/test/frontend/.next/server/webpack-runtime.js:33:42)
    at eval (./app/page.tsx:8:67)
    at (ssr)/./app/page.tsx (/Users/al/Documents/GitHub/test/frontend/.next/server/app/page.js:173:1)
    at Object.__webpack_require__ [as require] (/Users/al/Documents/GitHub/test/frontend/.next/server/webpack-runtime.js:33:42)
    at JSON.parse (<anonymous>)
digest: "1833689360"

For anyone still struggling with this. Don't bother loading polyfills yourself, it will not work.

You have to load the Legacy version of PdjJS worker which is already polyfilled. See https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions#faq-support

Like so:

import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url,
).toString();
DonikaV commented 3 months ago

@wojtekmaj Hey, sorry, is there any proper solution for this error? pdfjs.GlobalWorkerOptions.workerSrc =//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs;

and this import 'core-js/actual/promise';

Doesnt help. The latest version of Chrome and nextjs 14.2.5 and this error.

pulse-mind commented 3 months ago

Hi @wojtekmaj ,

First of all, well done for all the work done on this project.

I do not understand why this issue is closed. I am agree with @DonikaV .

I'm using this

export const pdfjsOptions = {
  workerSrc: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`, // use legacy worker for compatibility
  standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/standard_fonts/`, // enable standard fonts
  cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`, // enable character maps for non-latin languages
  cMapPacked: true,
};

// use external worker
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsOptions.workerSrc;

And I get the error on MANY recent browsers (tested on browserstack). If I add this piece of code just before then it works :

// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === 'undefined') {
  if (window)
      // @ts-expect-error This does not exist outside of polyfill which this is doing
      window.Promise.withResolvers = function () {
          let resolve, reject;
          const promise = new Promise((res, rej) => {
              resolve = res;
              reject = rej;
          });
          return { promise, resolve, reject };
      };
}

In my package.json I'm using : "react-pdf": "^9.1.0",

On my point of view the problem exists and is not solved. The problem should be fix properly of the documentation should be updated to alert the users to add the piece of code.

hsa1280 commented 3 months ago

Okay, so after a few tests, you still have to polyfill Promise.withResolvers because react-pdf uses non-legacy pdf.mjs even if you choose the legacy pdf worker. So legacy pdf worker + polyfilled Promise.withResolvers + react-pdf works as intended.

This is working for me, thanks for sharing the solution.

DonikaV commented 3 months ago

I think i fixed finally

getPdfWorker.js

import pfdWorkerPath from 'pdfjs-dist/legacy/build/pdf.worker.mjs';
import pfdWorkerMinPath from 'pdfjs-dist/legacy/build/pdf.worker.min.mjs';

export default process.env.NODE_ENV === 'production'
  ? pfdWorkerMinPath
  : pfdWorkerPath;

And then in PdfComponent

import workerSrc from '../utils/getPdfWorker';
pdfjs.GlobalWorkerOptions.workerSrc = `/${workerSrc}`;

But I have warnings

Attempted import error: 'pdfjs-dist/legacy/build/pdf.worker.mjs' does not contain a default export (imported as 'pfdWorkerPath').

Import trace for requested module:
./src/utils/getPdfWorker.ts
./src/components/PDFViewer.tsx
gjyoungjr commented 3 months ago

Anyone found a working fix for this? Tried Pollyfills and the legacy pdf worker and none of them worked.

armanmasangkay commented 3 months ago

This works for me

put this in the component where you use the Document component

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url,
).toString();

Add this on the page.tsx of the route.


if (typeof Promise.withResolvers === "undefined") {
    if (typeof window !== 'undefined') {
      // @ts-expect-error This does not exist outside of polyfill which this is doing
      window.Promise.withResolvers = function () {
        let resolve, reject
        const promise = new Promise((res, rej) => {
          resolve = res
          reject = rej
        })
        return { promise, resolve, reject }
      }
    } else {
      // @ts-expect-error This does not exist outside of polyfill which this is doing
      global.Promise.withResolvers = function () {
        let resolve, reject
        const promise = new Promise((res, rej) => {
          resolve = res
          reject = rej
        })
        return { promise, resolve, reject }
      }
    }
  }
Jdldeveloper commented 3 months ago

Loading a Polyfill and putting import './polyfills.mjs'; at the top of my next.config.mjs worked for me. This made Promise.withResolvers available in both browser and server environments.

Here's the code I used:

import 'core-js/full/promise/with-resolvers.js';

// Polyfill for environments where window is not available (e.g., server-side rendering)
if (typeof Promise.withResolvers === 'undefined') {
  if (typeof window !== 'undefined') {
    window.Promise.withResolvers = function () {
      let resolve, reject;
      const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });
      return { promise, resolve, reject };
    };
  } else {
    global.Promise.withResolvers = function () {
      let resolve, reject;
      const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });
      return { promise, resolve, reject };
    };
  }
}

For anyone else facing this, the above solution + using the cdn is the only thing which worked for me of all solutions in this thread.

pdfjs.GlobalWorkerOptions.workerSrc = //unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs;

I'd rather prefer to self-host this but nothing else worked here.

I have polyfills.mjs in src and did import '../polyfills.mjs' in _app.tsx.

Seems like there's 5 different working solutions for people regarding this issue, I hope it grabs the maintainers attention again since it seems like it needs a deeper dive.

codydaig commented 3 months ago

I am running node v22.6.0 and the upgrade to the latest react-pdf caused this issue in our test suite. If I'm running the latest node version, why am I getting this error?

jesus-maryapp commented 3 months ago

For anyone struggling with remix (v2.10.2 and nodejs 20 LTS).

This worked for me: I created a promisePolyfill.ts util function then imported it at the very top of the file that imports everything related to react-pdf or pdfjs-dist.

//promisePolyfill.ts
if (typeof Promise.withResolvers !== "function") {
  Promise.withResolvers = function <T>() {
    let resolve!: (value: T | PromiseLike<T>) => void;
    let reject!: (reason?: any) => void;
    const promise = new Promise<T>((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve, reject };
  };
}

export {};
//yourfile.client.tsx
import "~/utils/promisePolyfill";
import { useCallback, useRef, useState } from "react";
import { useResizeObserver } from "@wojtekmaj/react-hooks";
import { pdfjs, Document, Page } from "react-pdf";

import "react-pdf/dist/esm/Page/AnnotationLayer.css";
import "react-pdf/dist/esm/Page/TextLayer.css";
import { PDFToolbar } from "./pdfToolbar";
import { LoaderCircle } from "lucide-react";
import { IPDFViewerProps } from "~/types/pdfViewerTypes";

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  "/pdf.worker.min.mjs",
  import.meta.url,
).toString();

Do make sure that that your file is only rendering on the client side, so rename your file to : file.client.tsx

Also if you have issues with the typing for the promise polyfill remember to create your global.d.ts for it:

interface PromiseConstructor {
  withResolvers<T>(): {
    promise: Promise<T>;
    resolve: (value: T | PromiseLike<T>) => void;
    reject: (reason?: any) => void;
  };
}
ssxdev commented 3 months ago

page.tsx:

const PDFViewer = dynamic(
  () => import('./pdf-viewer').then(mod => mod.PDFViewer),
  { ssr: false }
)

pdf-viewer.tsx

"use client"

import { Document, Page, pdfjs } from 'react-pdf'

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString()

export function PDFViewer() {
  return (
    <Document>
      <Page />
    </Document>
  )
oaitbaziz commented 3 months ago

It fixed my tests execution which was failing since I added react-pdf to my project.

I've tried all solutions above nothing worked until I found your solution, thank you!

Dmytro-Uchkin-Ytree commented 3 months ago

@wojtekmaj The issue is still reproducible in some cases Chrome: 127 React: 18.2.0

image

The issue is related to pdf.js dependency. https://github.com/mozilla/pdf.js/issues?q=withResolvers

Dmytro-Uchkin-Ytree commented 3 months ago

@wojtekmaj The issue is still reproducible in some cases Chrome: 127 React: 18.2.0 image

The issue is related to pdf.js dependency. https://github.com/mozilla/pdf.js/issues?q=withResolvers

UPDATE: Tuned out Promise.withResolvers was overwritten by Zone.js (angular) (ZoneAwarePromise) Promise class whose old implementation does Not have withResolvers method.

Solution would be updating conflicting dependancies, especially if you're using Angular

dsternlicht commented 2 months ago

This works for me

put this in the component where you use the Document component

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url,
).toString();

Add this on the page.tsx of the route.


if (typeof Promise.withResolvers === "undefined") {
    if (typeof window !== 'undefined') {
      // @ts-expect-error This does not exist outside of polyfill which this is doing
      window.Promise.withResolvers = function () {
        let resolve, reject
        const promise = new Promise((res, rej) => {
          resolve = res
          reject = rej
        })
        return { promise, resolve, reject }
      }
    } else {
      // @ts-expect-error This does not exist outside of polyfill which this is doing
      global.Promise.withResolvers = function () {
        let resolve, reject
        const promise = new Promise((res, rej) => {
          resolve = res
          reject = rej
        })
        return { promise, resolve, reject }
      }
    }
  }

Worked for me as well. Thanks!

Bfaschat commented 2 months ago

Just wanted to throw some observations I discovered while testing this, For those still facing this issue while using react + Next JS Pages + Promise.withResolvers bypass

If you are planning to host your project on Vercel or any other hosting that doesn't have node 22.x support yet, below is the working solution.

@Kasui92 this error occurs because Vercel currently defaults to Node.js 20.x, while Promise.withResolvers() is available only in Node.js 22.x or later.

My project config uses the below packages:

  1. React version: 18.3.1
  2. Next.js version: 14.2.5
  3. Node.js version: v20.x
  4. react-pdf version: 9.1.0
  5. core-js version: 3.38.0

Here’s a polyfill solution for Promise.withResolvers() in both TypeScript and JavaScript:

TypeScript:

// utils/polyfilsResolver.tsx

/**
 * Polyfill for Promise.withResolvers if it's not available.
 */

export type PromiseWithResolvers<T> = {
  promise: Promise<T>;
  resolve: (value: T | PromiseLike<T>) => void;
  reject: (reason?: any) => void;
};

export function polyfillPromiseWithResolvers() {
  if (!Promise.withResolvers) {
    Promise.withResolvers = function <T>(): PromiseWithResolvers<T> {
      let resolve: (value: T | PromiseLike<T>) => void;
      let reject: (reason?: any) => void;

      const promise = new Promise<T>((res, rej) => {
        resolve = res;
        reject = rej;
      });

      return { promise, resolve: resolve!, reject: reject! };
    };
  }
}

Javascript

// utils/polyfilsResolver.js

/**
 * Polyfill for Promise.withResolvers if it's not available.
 */
export function polyfillPromiseWithResolvers() {
    if (!Promise.withResolvers) {
      Promise.withResolvers = function () {
        let resolve;
        let reject;

        const promise = new Promise((res, rej) => {
          resolve = res;
          reject = rej;
        });

        return { promise, resolve, reject };
      };
    }
  }

Now You can call polyfillPromiseWithResolvers() from _app.js or _app.jsx like below

// pages/_app.js or pages/_app.jsx

import { polyfillPromiseWithResolvers } from "@/utils/polyfilsResolver";

import 'core-js/full/promise/with-resolvers.js';

polyfillPromiseWithResolvers();

Now lets use the legacy version of react-pdf as the final touch

// Pdfviewer component 

pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;

Finally, lets build it for production to test

npm run build

Note: This is a working solution that i use on my active projects. It works 100% on vercel and other hostings that doesn't support node 20.x

Enjoy XD :100:

Kasui92 commented 2 months ago

@Bfaschat Oh, sounds like it's good for my case!

Thank you for the comments and the working solution!

Bfaschat commented 2 months ago

@Bfaschat Oh, sounds like it's good for my case!

Thank you for the comments and the working solution!

Merci <3

Bfaschat commented 2 months ago

Just wanted to throw some observations I discovered while testing this, For those still facing this issue while using react + Next JS Pages + Promise.withResolvers bypass

If you are planning to host your project on Vercel or any other hosting that doesn't have node 22.x support yet, below is the working solution.

@Kasui92 this error occurs because Vercel currently defaults to Node.js 20.x, while Promise.withResolvers() is available only in Node.js 22.x or later.

My project config uses the below packages:

  1. React version: 18.3.1
  2. Next.js version: 14.2.5
  3. Node.js version: v20.x
  4. react-pdf version: 9.1.0
  5. core-js version: 3.38.0

Here’s a polyfill solution for Promise.withResolvers() in both TypeScript and JavaScript:

TypeScript:

// utils/polyfilsResolver.tsx

/**
 * Polyfill for Promise.withResolvers if it's not available.
 */

export type PromiseWithResolvers<T> = {
  promise: Promise<T>;
  resolve: (value: T | PromiseLike<T>) => void;
  reject: (reason?: any) => void;
};

export function polyfillPromiseWithResolvers() {
  if (!Promise.withResolvers) {
    Promise.withResolvers = function <T>(): PromiseWithResolvers<T> {
      let resolve: (value: T | PromiseLike<T>) => void;
      let reject: (reason?: any) => void;

      const promise = new Promise<T>((res, rej) => {
        resolve = res;
        reject = rej;
      });

      return { promise, resolve: resolve!, reject: reject! };
    };
  }
}

Javascript

// utils/polyfilsResolver.js

/**
 * Polyfill for Promise.withResolvers if it's not available.
 */
export function polyfillPromiseWithResolvers() {
    if (!Promise.withResolvers) {
      Promise.withResolvers = function () {
        let resolve;
        let reject;

        const promise = new Promise((res, rej) => {
          resolve = res;
          reject = rej;
        });

        return { promise, resolve, reject };
      };
    }
  }

Now You can call polyfillPromiseWithResolvers() from _app.js or _app.jsx like below

// pages/_app.js or pages/_app.jsx

import { polyfillPromiseWithResolvers } from "@/utils/polyfilsResolver";

import 'core-js/full/promise/with-resolvers.js';

polyfillPromiseWithResolvers();

Now lets use the legacy version of react-pdf as the final touch

// Pdfviewer component 

pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;

Finally, lets build it for production to test

npm run build

Note: This is a working solution that i use on my active projects. It works 100% on vercel and other hostings that doesn't support node 20.x

Enjoy XD πŸ’―

Hey everyone :) Make sure you are running npm run build or yarn run build using node 20.x not 22.x

Trying to use Node 22.x gives a new error, i didn't have time to debug it so i defaulted back to 20.x

I hope my solution above works as expected

Happy coding

robiulhr commented 2 months ago

Perhaps using a legacy worker would help? πŸ€”

I tried using a legacy worker, but sadly that did not help. That being said, the following polyfill worked like a charm:

// @ts-expect-error This does not exist outside of polyfill which this is doing
if (typeof Promise.withResolvers === 'undefined') {
    if (window)
        // @ts-expect-error This does not exist outside of polyfill which this is doing
        window.Promise.withResolvers = function () {
            let resolve, reject;
            const promise = new Promise((res, rej) => {
                resolve = res;
                reject = rej;
            });
            return { promise, resolve, reject };
        };
}

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
    'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
    import.meta.url
).toString();

Once I added this, it worked fantastically! 😁

This worked for me.

zeeklog commented 2 months ago

Super hacky, but this is how we added the polyfill to the pdfjs worker itself, in vite.config.ts:

function transformPdfJsWorker(): Plugin {
  return {
    name: 'transform-pdf-js-worker',
    generateBundle(options, bundle) {
      for (const [fileName, chunkOrAsset] of Object.entries(bundle)) {
        if (!fileName.includes('pdf.worker') || chunkOrAsset.type !== 'asset') {
          continue
        }
        const prepend = Buffer.from(
          `if (typeof Promise.withResolvers === "undefined") {
            Promise.withResolvers = function () {
              let resolve, reject
              const promise = new Promise((res, rej) => {
                resolve = res
                reject = rej
              })
              return { promise, resolve, reject }
            }
          }
          `,
          'utf-8'
        )
        const sourceBuffer = Buffer.isBuffer(chunkOrAsset.source)
          ? chunkOrAsset.source
          : Buffer.from(chunkOrAsset.source)
        chunkOrAsset.source = Buffer.concat([prepend, sourceBuffer])
      }
    },
  }
}

export default defineConfig({
  plugins: [
    transformPdfJsWorker(),
  ],
})

Excellent!!! It worked for me.

Alwaz commented 2 months ago

I was able to resolve the issue by setting the dynamic component to load without server-side rendering (SSR). const PdfViewerComponent = dynamic(() => import("./PdfViewer"), { ssr: false, });

and make sure in next.config.js enable this config.resolve.alias.canvas = false;

Thankyouuu soo muchhh you saved me!!!

luuknnh commented 1 month ago

page.tsx:

const PDFViewer = dynamic(
  () => import('./pdf-viewer').then(mod => mod.PDFViewer),
  { ssr: false }
)

pdf-viewer.tsx

"use client"

import { Document, Page, pdfjs } from 'react-pdf'

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString()

export function PDFViewer() {
  return (
    <Document>
      <Page />
    </Document>
  )

This still doesn't work for me when I use Nextjs 14.2.11 :(

When I run npm run build I get a craaazy long error with lots of whitespace and this on the bottom: `----

Caused by: 0: failed to parse input file 1: Syntax Error

Build failed because of webpack errors

rlawnsrud0509 commented 1 month ago

νŽ˜μ΄μ§€.tsx:

const PDFViewer = dynamic(
  () => import('./pdf-viewer').then(mod => mod.PDFViewer),
  { ssr: false }
)

pdf-λ·°μ–΄.tsx

"use client"

import { Document, Page, pdfjs } from 'react-pdf'

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString()

export function PDFViewer() {
  return (
    <Document>
      <Page />
    </Document>
  )

Nextjs 14.2.11을 μ‚¬μš©ν•΄λ„ μ—¬μ „νžˆ μž‘λ™ν•˜μ§€ μ•Šμ•„μš” :(

npm run buildλ₯Ό μ‹€ν–‰ν•˜λ©΄ 곡백이 λ§Žμ€ μ—„μ²­λ‚˜κ²Œ κΈ΄ 였λ₯˜κ°€ λ°œμƒν•˜κ³  ν•˜λ‹¨μ— λ‹€μŒκ³Ό 같은 λ‚΄μš©μ΄ ν‘œμ‹œλ©λ‹ˆλ‹€. `----

원인: 0: μž…λ ₯ 파일 ꡬ문 뢄석 μ‹€νŒ¨ 1: ꡬ문 였λ₯˜

webpack 였λ₯˜λ‘œ 인해 λΉŒλ“œκ°€ μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.

add this code to next.config.mjs

webpack: (config) => {
    config.optimization.minimize = false;

    return config;
},
Bumboobee commented 1 month ago

if (typeof Promise.withResolvers === "undefined") { if (typeof window !== 'undefined') { // @ts-expect-error This does not exist outside of polyfill which this is doing window.Promise.withResolvers = function () { let resolve, reject const promise = new Promise((res, rej) => { resolve = res reject = rej }) return { promise, resolve, reject } } } else { // @ts-expect-error This does not exist outside of polyfill which this is doing global.Promise.withResolvers = function () { let resolve, reject const promise = new Promise((res, rej) => { resolve = res reject = rej }) return { promise, resolve, reject } } } }

After many tried so hard with the previous answers and the error still remaning, @armanmasangkay solution works for me just well, thanks a lot bro!