You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RemixJs+TypeScript环境下pdfjs-dist Worker导入及配置问题求助

在Remix.js + TypeScript中配置pdfjs-dist并读取PDF文本

问题分析

你遇到的核心问题有两个:

  • GlobalWorkerOptions配置错误,直接导入worker模块而非指定可访问的脚本URL
  • worker文件无默认导出导致导入报错

下面提供两种可行的解决方案,均适配Remix+TS环境。


前置准备

确保依赖版本匹配:

  • 安装核心库:npm install pdfjs-dist
  • 安装对应类型声明:npm install -D @types/pdfjs-dist

注意:@types/pdfjs-dist版本需与pdfjs-dist主版本一致,避免类型不兼容


方案一:使用workerPort(推荐,ES模块友好)

利用Remix/Vite的?worker后缀语法,直接将worker作为模块导入并实例化,无需全局配置。

'use client'; // 必须添加,因为涉及浏览器API(FileReader、Worker)

import { getDocument } from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?worker';
import { useState } from 'react';

export default function DocumentTranslation() {
  const [characterCount, setCharacterCount] = useState(0);
  const [downloadFileName, setDownloadFileName] = useState('');

  const handleFileSelect = (file: File) => {
    if (file.type !== 'application/pdf') return;

    const reader = new FileReader();
    reader.onload = async (e) => {
      try {
        const typedArray = new Uint8Array(e.target.result as ArrayBuffer);
        
        // 直接传入worker实例到getDocument
        const pdfDoc = await getDocument({
          data: typedArray,
          workerPort: new pdfjsWorker()
        }).promise;

        let textContent = '';
        for (let i = 1; i <= pdfDoc.numPages; i++) {
          const page = await pdfDoc.getPage(i);
          const textData = await page.getTextContent();
          // 类型断言确保正确访问str属性
          textContent += textData.items.map(item => (item as { str: string }).str).join(' ');
        }

        setCharacterCount(textContent.length);
        const baseName = file.name.split('.').slice(0, -1).join('.');
        setDownloadFileName(`${baseName}_processed.pdf`);
      } catch (err) {
        console.error('读取PDF失败:', err);
      }
    };
    reader.readAsArrayBuffer(file);
  };

  // ...组件渲染逻辑
  return (
    <div>
      <input type="file" accept="application/pdf" onChange={(e) => e.target.files && handleFileSelect(e.target.files[0])} />
      {characterCount > 0 && <p>字符数:{characterCount}</p>}
      {downloadFileName && <p>处理后文件名:{downloadFileName}</p>}
    </div>
  );
}

方案二:配置GlobalWorkerOptions.workerSrc

通过指定worker脚本的URL(本地或CDN)来全局配置,适合需要共享worker配置的场景。

步骤1:复制worker文件到public目录

将node_modules/pdfjs-dist/build/pdf.worker.min.mjs复制到Remix项目的public目录下,这样浏览器可以直接访问到该脚本。

步骤2:代码实现

'use client';

import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist';
import { useState } from 'react';

// 仅在浏览器环境中配置worker
if (typeof window !== 'undefined') {
  // 指向public目录下的worker文件
  GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs';
  // 也可以使用CDN地址,例如:
  // GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.worker.min.mjs';
}

export default function DocumentTranslation() {
  const [characterCount, setCharacterCount] = useState(0);
  const [downloadFileName, setDownloadFileName] = useState('');

  const handleFileSelect = (file: File) => {
    if (file.type !== 'application/pdf') return;

    const reader = new FileReader();
    reader.onload = async (e) => {
      try {
        const typedArray = new Uint8Array(e.target.result as ArrayBuffer);
        // 无需额外配置worker,直接调用getDocument
        const pdfDoc = await getDocument({ data: typedArray }).promise;

        let textContent = '';
        for (let i = 1; i <= pdfDoc.numPages; i++) {
          const page = await pdfDoc.getPage(i);
          const textData = await page.getTextContent();
          textContent += textData.items.map(item => (item as { str: string }).str).join(' ');
        }

        setCharacterCount(textContent.length);
        const baseName = file.name.split('.').slice(0, -1).join('.');
        setDownloadFileName(`${baseName}_processed.pdf`);
      } catch (err) {
        console.error('读取PDF失败:', err);
      }
    };
    reader.readAsArrayBuffer(file);
  };

  // ...组件渲染逻辑
  return (
    <div>
      <input type="file" accept="application/pdf" onChange={(e) => e.target.files && handleFileSelect(e.target.files[0])} />
      {characterCount > 0 && <p>字符数:{characterCount}</p>}
      {downloadFileName && <p>处理后文件名:{downloadFileName}</p>}
    </div>
  );
}

关键注意点

  1. 必须添加'use client':Remix默认是服务器组件,而FileReader、Worker等API只能在浏览器环境运行,因此组件需声明为客户端组件。
  2. 类型断言:getTextContent()返回的items类型可能需要手动断言为包含str属性的对象,避免TS报错。
  3. 版本匹配:确保pdfjs-dist和@types/pdfjs-dist版本一致,否则可能出现GlobalWorkerOptions声明缺失的问题。

内容的提问来源于stack exchange,提问作者YEAHH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 23:48:13