Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failed to compile due to Webpack errors - Module parse failed: Unexpected character '�' using Docusaurus #1765

Open
4 tasks done
tobiasbueschel opened this issue Apr 21, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@tobiasbueschel
Copy link

Before you start - checklist

  • I followed instructions in documentation written for my React-PDF version
  • I have checked if this bug is not already reported
  • I have checked if an issue is not listed in Known issues
  • If I have a problem with PDF rendering, I checked if my PDF renders properly in PDF.js demo

Description

Context: using react-pdf in the latest Docusaurus version 3.2.1

  • running npm run build works for the client but not for the server, the following error shows up:
image
[ERROR] Error: Unable to build website for locale en.
    at tryToBuildLocale (/test-repo/node_modules/@docusaurus/core/lib/commands/build.js:53:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async /test-repo/node_modules/@docusaurus/core/lib/commands/build.js:64:9
    at async mapAsyncSequential (/test-repo/node_modules/@docusaurus/utils/lib/jsUtils.js:20:24)
    at async Command.build (/test-repo/node_modules/@docusaurus/core/lib/commands/build.js:62:5) {
  [cause]: Error: Failed to compile due to Webpack errors.
  Module parse failed: Unexpected character '�' (1:0)
  You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
  (Source code omitted for this binary file)
      at /test-repo/node_modules/@docusaurus/core/lib/webpack/utils.js:230:24
      at /test-repo/node_modules/webpack/lib/MultiCompiler.js:596:14
      at processQueueWorker (/test-repo/node_modules/webpack/lib/MultiCompiler.js:533:6)
      at process.processTicksAndRejections (node:internal/process/task_queues:77:11)

However, running npm start works without any problems and I can see the PDF rendered correctly in the Docusaurus website.

Steps to reproduce

  1. Create a new docusaurus skeleton setup: npx create-docusaurus@latest my-website classic
  2. npm install react-pdf
  3. Add a new PdfViewer component to src/components/PdfViewer.jsx
import React, { useState } from 'react';
import { pdfjs, Document, Page } from 'react-pdf';

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

export default function PdfViewer({ fileName }) {
  const [numPages, setNumPages] = useState(null);

  function onDocumentLoadSuccess({ numPages }) {
    setNumPages(numPages);
  }

  function openPdf() {
    window.open(fileName, '_blank', 'fullscreen=yes');
  }

  return (
    <>
      <button onClick={openPdf} className="button button--primary mb-3">
        Open PDF here
      </button>
      <Document file={fileName} onLoadSuccess={onDocumentLoadSuccess}>
        {Array.from(new Array(numPages), (el, index) => (
          <Page
            key={`page_${index + 1}`}
            pageNumber={index + 1}
            renderAnnotationLayer={false}
            renderTextLayer={false}
            onItemClick={openPdf}
            className="w-full [&>canvas]:!w-full [&>canvas]:!h-full"
          />
        ))}
      </Document>
    </>
  );
}

(I've also tried with <BrowserOnly /> inside the component, but that led to the same issue)

  1. Add the following to your docusaurus.config.js:
// ...
plugins: [
    async function configureWebpack(context, options) {
      return {
        name: 'docusaurus-webpack-config',
        configureWebpack(config, isServer, utils) {
          if (isServer) {
            return {
              resolve: {
                alias: {
                  canvas: false,
                  encoding: false,
                },
              },
            };
          } else {
            return;
          }
        },
      };
    },
]
// ...

(reference from: https://github.com/syed-ahmed/docusaurus-plugin-react-pdf/blob/main/src/index.ts#L31-L42)

  1. In any MDX doc, import the component and render a PDF.
<PdfViewer fileName={require('./docs/example.pdf').default} />

Expected behavior

The build succeeds.

Actual behavior

The build fails with the above mentioned error message.

Additional information

Potentially related issues & comments:

Environment

  • Browser (if applicable): N/A
  • React-PDF version: 7.7.1
  • React version: 18.2.0
  • Webpack version: 5.91.0
  • Node version: 18.14.2
  • Docusaurus version: 3.2.1
@tobiasbueschel tobiasbueschel added the bug Something isn't working label Apr 21, 2024
@tobiasbueschel tobiasbueschel changed the title Failed to compile due to Webpack errors - Module parse failed: Unexpected character '�' Docusaurus: Failed to compile due to Webpack errors - Module parse failed: Unexpected character '�' Apr 21, 2024
@tobiasbueschel tobiasbueschel changed the title Docusaurus: Failed to compile due to Webpack errors - Module parse failed: Unexpected character '�' Failed to compile due to Webpack errors - Module parse failed: Unexpected character '�' using Docusaurus Apr 21, 2024
@wojtekmaj
Copy link
Owner

This definitely look like a flavor of #1508 and #1620.

You did everything just the way I'd do. I'm puzzled. If importing PdfViewer asynchronously in <BrowserOnly> doesn't help, maybe it'd be better to ask on Docusaurus repo why that could be the case? I don't see any reason for BrowserOnly to be executed on the server side!

@hzhang20902
Copy link

I just had this specific problem with almost the exact same setup. The differences being that as of now, July 27, 2024, I have more recent versions of React-PDF (9.1.0); webpack (5.93.0); and docusaurus(3.4). That being said, I built my client within a docker container, but the issues encountered were all from the build step, or a result of the build.

This solution I imagine should work also for a Next.js app that has static site generation issues (replace lazy with dynamic, and maybe also browserOnly with something else) because the plugin workaround you listed above led me to a solution to the build issue, but created a render issue, which is what the Promise.withResolvers part from the #1508 thread is for. Then, the page rendered correctly and the component could input a pdf to load, but could not load it, which led to the worker import workaround, which led to resize issues, which led to recreating the useResizeObserver hook and then it all finally worked:

  • do an entirely clean npx for the docusaurus;
  • reinstall all packages EXCEPT for the @wojtekmaj/react-hooks;
  • keep all things related to react-pdf isolated as a single component; do NOT add configuration for webpack;
  • within the script for the component, add this after imports and before pdfjs gets the worker:
if (typeof Promise.withResolvers === "undefined") {
    if (window) {
      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 }
      }
    }
  }
  • import your worker directly from a CDN, and match the version exactly by using the version prop:
pdfjs.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.mjs`
  • make sure the PDF viewer component is the default export within the script (rafce not rafc);
  • create a loader component and lazyload the PDF viewer component:
import React, { lazy, Suspense } from 'react'

const AnyName = lazy(() => import("./PDFViewerComponent"))

export const StaticPDF = () => {  
    return (
    <Suspense fallback={<p>Loading...</p>}>
        <AnyName />        
    </Suspense>
    );
  };
  • import that component into the MDX file, and wrap it in browseronly:
# MDX Page

### Content

 <BrowserOnly>
        {() => <StaticPDF />}
 </BrowserOnly>
  
import { StaticPDF } from "@site/src/components/StaticPDF"
import BrowserOnly from '@docusaurus/BrowserOnly'
  • recreate useResizeObserver hook as a separate file from @wojtekmaj source code:
import { useEffect } from 'react';

/**
 * Observes a given element using ResizeObserver.
 *
 * @param {Element} [element] Element to attach ResizeObserver to
 * @param {ResizeObserverOptions} [options] ResizeObserver options. WARNING! If you define the
 *   object in component body, make sure to memoize it.
 * @param {ResizeObserverCallback} observerCallback ResizeObserver callback. WARNING! If you define
 *   the function in component body, make sure to memoize it.
 * @returns {void}
 */
export default function useResizeObserver(
  element: Element | null,
  options: ResizeObserverOptions | undefined,
  observerCallback: ResizeObserverCallback,
): void {
  useEffect(() => {
    if (!element || !('ResizeObserver' in window)) {
      return undefined;
    }

    const observer = new ResizeObserver(observerCallback);

    observer.observe(element, options);

    return () => {
      observer.disconnect();
    };
  }, [element, options, observerCallback]);
}

This should make everything work. I am very tired right now though and may have forgotten to mention other things I did, but hopefully this helps you and others who have the same issue.

@impiia
Copy link

impiia commented Jul 28, 2024

I just had this specific problem with almost the exact same setup. The differences being that as of now, July 27, 2024, I have more recent versions of React-PDF (9.1.0); webpack (5.93.0); and docusaurus(3.4). That being said, I built my client within a docker container, but the issues encountered were all from the build step, or a result of the build.

This solution I imagine should work also for a Next.js app that has static site generation issues (replace lazy with dynamic, and maybe also browserOnly with something else) because the plugin workaround you listed above led me to a solution to the build issue, but created a render issue, which is what the Promise.withResolvers part from the #1508 thread is for. Then, the page rendered correctly and the component could input a pdf to load, but could not load it, which led to the worker import workaround, which led to resize issues, which led to recreating the useResizeObserver hook and then it all finally worked:

  • do an entirely clean npx for the docusaurus;
  • reinstall all packages EXCEPT for the @wojtekmaj/react-hooks;
  • keep all things related to react-pdf isolated as a single component; do NOT add configuration for webpack;
  • within the script for the component, add this after imports and before pdfjs gets the worker:
if (typeof Promise.withResolvers === "undefined") {
    if (window) {
      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 }
      }
    }
  }
  • import your worker directly from a CDN, and match the version exactly by using the version prop:
pdfjs.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.mjs`
  • make sure the PDF viewer component is the default export within the script (rafce not rafc);
  • create a loader component and lazyload the PDF viewer component:
import React, { lazy, Suspense } from 'react'

const AnyName = lazy(() => import("./PDFViewerComponent"))

export const StaticPDF = () => {  
    return (
    <Suspense fallback={<p>Loading...</p>}>
        <AnyName />        
    </Suspense>
    );
  };
  • import that component into the MDX file, and wrap it in browseronly:
# MDX Page

### Content

 <BrowserOnly>
        {() => <StaticPDF />}
 </BrowserOnly>
  
import { StaticPDF } from "@site/src/components/StaticPDF"
import BrowserOnly from '@docusaurus/BrowserOnly'
  • recreate useResizeObserver hook as a separate file from @wojtekmaj source code:
import { useEffect } from 'react';

/**
 * Observes a given element using ResizeObserver.
 *
 * @param {Element} [element] Element to attach ResizeObserver to
 * @param {ResizeObserverOptions} [options] ResizeObserver options. WARNING! If you define the
 *   object in component body, make sure to memoize it.
 * @param {ResizeObserverCallback} observerCallback ResizeObserver callback. WARNING! If you define
 *   the function in component body, make sure to memoize it.
 * @returns {void}
 */
export default function useResizeObserver(
  element: Element | null,
  options: ResizeObserverOptions | undefined,
  observerCallback: ResizeObserverCallback,
): void {
  useEffect(() => {
    if (!element || !('ResizeObserver' in window)) {
      return undefined;
    }

    const observer = new ResizeObserver(observerCallback);

    observer.observe(element, options);

    return () => {
      observer.disconnect();
    };
  }, [element, options, observerCallback]);
}

This should make everything work. I am very tired right now though and may have forgotten to mention other things I did, but hopefully this helps you and others who have the same issue.

For me, this worked:
pdfjs.GlobalWorkerOptions.workerSrc = https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.mjs;

and adding the dynamic import:

const PdfViewer = dynamic(
  () => import('../../../components/pdfViewer/component').then((mod) => mod.PdfViewer as any),
  { ssr: false }
);

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants