pdf를 리액트에서 보여주기

Sharlotte ·2022년 11월 23일
4

구글링을 포기한 이 아해에게 npm 링크를 줄려다가 그냥 직접 헤딩까지 하면 죄책감으로 다음부턴 직접 검색해볼 수도 있단 희망과 개인적으로 pdf view에 충동적 흥미가 느껴져 바닥부터 시작하도록 하겠다.

0. CNA

Create Next App는 Next.js의 CRA다.

npx create-next-app <project-name>

or

yarn create next-app <project-name>

로 간단히 Next.js 프로젝트를 시작할 수 있다.
CRA는 typescript flag를 넣어야 하는 것과 달리 CNA는 직접 물어본다.

왜 CNA를 사용하나요?
CNA에서는 몇 초 안에 새 Next.js 앱을 만들 수 있습니다. Next.js의 제작자들에 의해 공식적으로 유지되고 있으며, 여러 가지 혜택이 포함되어 있습니다.

  • 상호작용 체험 - 위 사진과 같이 CNA는 개발자와 상호작용하며 프로젝트를 설정합니다.
  • 의존성 없음 - create-next-app은 의존성을 갖고 있지 않습니다. (상호작용을 제외하고) 프로젝트 초기화는 1초 내에 이루어질겁니다.
  • 오프라인 지원 - create-next-app은 자동적으로 오프라인임을 감지하고 로컬 패키지 케시를 이용해 프로젝트를 생성합니다.
  • 예시 지원 - create-next-app--example-api-routes와 같이 특정 flag를 갖고 있으면 해당 예시의 프로젝트를 생성합니다. https://github.com/vercel/next.js/tree/canary/examples 에서 더 많은 예시 코드를 알아보세요.
  • 테스트됨 - create-next-app은 Next.js 모노레포의 일부이며 Next.js 자신과 똑같은 통합 테스트를 통해 테스트되며 매 릴리즈마다 작동 여부를 확인합니다.
    https://nextjs.org/docs/api-reference/create-next-app

생성시 위와 같이 불필요한 코드가 다수 보인다. /styles와 /pages/api를 모두 지우겠다.

1. PDF View Library

구글로 찾아보니 최상단에 react-pdf가 있었다. 배포도 18시간 전으로 신선하고, 스타도 6.5k에 매주 41만이 다운로드했다고 하니 믿음직한 라이브러리인 것 같다.

이 라이브러리는 PDF를 읽는 패키지입니다. 리액트를 이용해 PDF를 만드는 것을 바란다면 @react-pdf/renderer로 찾아가봐야 할겁니다.
https://www.npmjs.com/package/react-pdf

이 글에서의 목적은 어떻게든 pdf를 보여주기만 하면 되는 것이므로 react-pdf를 사용한다.

시련: Cannot find module pdf ts(2307)

타입스크립트는 기본적으로 pdf 모듈을 받아들이지 않는 것 같다. 찾아보니 전역에서 타입 명시를 따로 해주면 된다고 한다. 추가로 알아보니 txt나 html와 같이 타스가 안받아들이는 파일들은 모두 다 이렇게 전역에서 타입 명시를 하면 된다고 한다.

I was able to solve the issue by adding global.d.ts in my @types directory located under src thanks to this post.
https://github.com/facebook/create-react-app/issues/8021.

@types/를 따로 만들바에야 최상단에 갖다넣는게 더 편하다. 이미 tsconfig.complierOptions.types의 진입점이 최상단이기 때문이다.

깨끗하다.

시련: Failed to load PDF file.

이 문제의 가장 일반적인 원인은 네트워크 문제입니다. PDF 파일을 로드할 URL이 올바른지 확인해보세요.
https://github.com/wojtekmaj/react-pdf/wiki/Frequently-Asked-Questions#when-im-trying-to-load-a-pdf-file-from-an-external-source-im-given-a-message-failed-to-load-pdf-file

이 에러는 라이브러리 공식 위키의 잦은 질문에 있을 정도로 흔한 문제다. 예상하건데 경로 문제일 것이 확실하다. 왜냐하면 라이브러리 리드미에 따라 코드를 작성도 해보고, 경로를 정확히 변경해도 작동하지 않았기 때문이다.

// sample.pdf는 pdf-example/public/sample.pdf
import React from 'react'
import { Document, Page } from 'react-pdf'

export default function Home() {
  const [numPages, setNumPages] = React.useState(null);
  const [pageNumber, setPageNumber] = React.useState(1);

  const onDocumentLoaded = ({ numPages }) => setNumPages(numPages);

  return (
    <div>
      <Document file='./sample.pdf' onLoadSuccess={onDocumentLoaded}>
        <Page pageNumber={pageNumber} />
      </Document>
      Page {pageNumber} of {numPages}
    </div>
  )
}

시도 - import 동원

Place the pdf into a folder in /src. Import it like a component.
https://stackoverflow.com/questions/48572896/react-how-to-open-pdf-file-as-a-href-target-blank

import 문으로 가져와서 file prop에 넣는 방법을 제시받았다. 하지만

Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type,

react-pdf 리드미에선 웹팩4일 경우 pdf 로더를 설치해야 한다고 말한다.

Webpack 4


If you want to use React-PDF with Webpack 4, you'll need to manually install file-loader package.
https://www.npmjs.com/package/react-pdf

하지만 Next.js는 웹팩5를 기본으로 갖고 있다.

Next.js has adopted webpack 5 as the default for compilation.
https://nextjs.org/docs/messages/webpack5

next.config.js에서 webpack5: true를 함에도 불구하고 pdf 모듈을 불러오지 못하는 것을 보아 웹팩이 5일지라도 로더를 설치해야 하는 것 같다.

시도 - file loader

Use the webpack file-loader module to process static content
https://stackoverflow.com/questions/36643649/serving-static-pdf-with-react-webpack-file-loader

스택 오버플로에선 file-loader를 쓰라고 하지만, 웹팩5에선 Deprecated되었다.

DEPRECATED for v5: please consider migrating to asset modules.
https://v4.webpack.js.org/loaders/file-loader/

Asset Modules is a type of module that allows one to use asset files (fonts, icons, etc) without configuring additional loaders.
Prior to webpack 5 it was common to use file-loader to emit a file into the output directory
Asset Modules는 추가적인 loader 모듈 없이 폰트, 아이콘과 같은 어셋 파일들을 사용하는걸 허용해주는 모듈 타입입니다.
https://webpack.js.org/guides/asset-modules

module: {
   rules: [
     {
       test: /\.png/,
       type: 'asset/resource'
     }
   ]
 },

All .png files will be emitted to the output directory and their paths will be injected into the bundles. besides, you can customize outputPath and publicPath for them.
모든 .png 파일이 outputPath로 전송되고 해당 경로가 번들에 삽입됩니다. 또한 outputPathpublicPath를 임의로 지정할 수 있습니다.
https://webpack.js.org/guides/asset-modules/#resource-assets

.png 파일이 asset/resource 타입 모듈에 의해 임포트되는 예시를 응용하여 이참에 다른 리소스까지 모두 임포트될 수 있도록 웹팩 규칙을 추가할 생각이다. 이때 웹팩 규칙 파일을 따로 생성하지 않아도 된다. next.config.js웹팩 규칙 설정 함수도 가지고 있다.

// in nextConfig
  webpack(config) {
    config.module.rules.push({
      test: /\.(pdf|gif|png|jpe?g|svg)$/,
      type: 'asset/resource'
    })

    return config;
  }

시련 - 그럼에도 불구하고 파일을 못불러옴

콘솔에 한번 찍어봐서 import한 리액트 컴포넌트 형태의 pdf가 있기는 한지 확인해보았다.

static의 media 안에 고스란히 들어있다. 저런 public 리소스같은 경우엔 결국 URL인지라 직접 들어가서 확인해볼 수 있는데,

책임 소재가 react-pdf인 것이 확실해지는 순간이다.

심지어 완전한 url조차도 못가져온다. 그런데 이때 두번째 시련에서 인용한 react-pdf의 Failed to load PDF file.에 대한 에러의 설명에서 네크워크 에러라는 것이 순간 떠올라서 개발자 도구에서 네트워크 탭을 보니
오 이런 세상에, 뭔가가 있다. 구글링해본 결과 이미 깃헙 이슈로 올라온게 여럿 있었고, 거기서 핵심 솔루션을 찾았다.

Dear Friends,
after I spent some time with this issue here are my solutions that worked.
If you have a free configurable webpack config,this is what worked.

{ 
  test: /pdf\.worker\.min\.js$/, 
  loader: 'url-loader', 
  options: { 
    name: 'pdfWorker.[ext]', 
    limit: 1000, 
  }, 
  type: 'javascript/auto', 
}

https://github.com/wojtekmaj/react-pdf/issues/97#issuecomment-940069915

...하지만 난 webpack5를 사용중이고 곧 url-loader는 Deprecated될 모듈이다. 그 위 코멘트를 보면

For anyone trying to enable it in Next.js with webpack5

Tell webpack to treat pdf.worker.js as an asset

...
webpack(config) {
  config.module.rules.push({
    test: /pdfjs-dist\/build\/pdf\.worker\.js$/,
    type: "asset/resource",
    generator: {
      filename: "static/chunks/[name].[hash][ext]",
    },
  });
}
...

import it in your code and set URL manually

import { Document, Page, pdfjs } from "react-pdf";
import url from "pdfjs-dist/build/pdf.worker";
pdfjs.GlobalWorkerOptions.workerSrc = url;

https://github.com/wojtekmaj/react-pdf/issues/97#issuecomment-920209892

하지만 pdfjs-dist는 npm 모듈이고 번들 크기를 늘릴게 뻔하다. 하지만 구글링을 더 해본 결과 스택오버플로우에서 또다른 솔루션을 찾을 수 있었다.

If you are using create-react-app biolerplate, change your code to:

import { Document, Page, pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

It will work.
Thanks @frankenapps for suggesting a good practice.
Also Refer to this for create-react-app

https://stackoverflow.com/questions/60793727/failed-to-load-pdf-file-in-react-js

이제 어떠한 에러 없이 pdf가 정상적으로 불러와진다.

profile
샤르르르

0개의 댓글