refactor(pdf): centralize pdf.js runtime configuration and asset resolution

Move pdf.js worker and standard font resolution logic into dedicated
utility functions for improved maintainability and clarity. Replace
inline workerSrc setup with a reusable configurePdfjsNodeRuntime helper.
Update Next.js config to use a shared asset trace list for pdf.js runtime
files, reducing duplication and simplifying future updates.
This commit is contained in:
Richard R 2026-06-05 10:40:38 -06:00
parent 2f99b3987e
commit 0e29450b0d
4 changed files with 60 additions and 26 deletions

View file

@ -3,7 +3,7 @@ import type { ParsedPdfDocument, ParsedPdfPage } from '../types/parsed-pdf';
import { ensureModel } from './model';
import { runLayoutModel } from './runLayoutModel';
import { mergeTextWithRegions } from './merge';
import { resolvePdfjsStandardFontDataUrl } from './pdfjs-runtime';
import { configurePdfjsNodeRuntime, resolvePdfjsStandardFontDataUrl } from './pdfjs-runtime';
import { PDF_PARSER_VERSION } from './parser-version';
import { stitchCrossPageBlocks } from './stitch';
import { renderPage } from './render';
@ -36,10 +36,7 @@ export async function parsePdf(input: ParsePdfInput): Promise<ParsedPdfDocument>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
if (pdfjs.GlobalWorkerOptions) {
pdfjs.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.mjs';
pdfjs.GlobalWorkerOptions.workerPort = null;
}
configurePdfjsNodeRuntime(pdfjs);
const standardFontDataUrl = resolvePdfjsStandardFontDataUrl();
const loadingTask = pdfjs.getDocument({

View file

@ -1,14 +1,39 @@
import fs from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
let cachedStandardFontDataUrl: string | null = null;
const require = createRequire(import.meta.url);
const pdfjsPackageName = 'pdfjs-dist';
function candidatePackageRoots(): string[] {
const roots = new Set<string>();
let dir = process.cwd();
for (let i = 0; i < 8; i += 1) {
roots.add(path.join(dir, 'node_modules', pdfjsPackageName));
const next = path.dirname(dir);
if (next === dir) break;
dir = next;
}
return [...roots];
}
function resolvePdfjsPackageFile(relativePath: string): string {
const normalizedRelativePath = relativePath.replace(/^\/+/, '');
const candidates = candidatePackageRoots().map((root) => path.join(root, normalizedRelativePath));
const found = candidates.find((candidate) => fs.existsSync(candidate));
if (found) return found;
throw new Error(
`pdfjs-dist file not found: ${normalizedRelativePath}; checked ${candidates.join(', ')}`,
);
}
export function resolvePdfjsStandardFontDataUrl(): string {
if (cachedStandardFontDataUrl) return cachedStandardFontDataUrl;
const pdfjsPackageDir = path.dirname(require.resolve('pdfjs-dist/package.json'));
const pdfjsPackageDir = path.dirname(resolvePdfjsPackageFile('package.json'));
const standardFontDir = path.join(pdfjsPackageDir, 'standard_fonts');
if (!fs.existsSync(standardFontDir)) {
@ -18,3 +43,18 @@ export function resolvePdfjsStandardFontDataUrl(): string {
cachedStandardFontDataUrl = `${standardFontDir.replace(/\/?$/, '/')}`;
return cachedStandardFontDataUrl;
}
export function resolvePdfjsWorkerSrc(): string {
return pathToFileURL(resolvePdfjsPackageFile('legacy/build/pdf.worker.mjs')).href;
}
export function configurePdfjsNodeRuntime(pdfjs: {
GlobalWorkerOptions?: {
workerSrc?: string;
workerPort?: unknown;
};
}): void {
if (!pdfjs.GlobalWorkerOptions) return;
pdfjs.GlobalWorkerOptions.workerSrc = resolvePdfjsWorkerSrc();
pdfjs.GlobalWorkerOptions.workerPort = null;
}

View file

@ -1,5 +1,5 @@
import type { Canvas } from '@napi-rs/canvas';
import { resolvePdfjsStandardFontDataUrl } from './pdfjs-runtime';
import { configurePdfjsNodeRuntime, resolvePdfjsStandardFontDataUrl } from './pdfjs-runtime';
type CanvasRuntime = {
DOMMatrixCtor: unknown;
@ -106,11 +106,7 @@ export async function renderPage({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
if (pdfjs.GlobalWorkerOptions) {
pdfjs.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.mjs';
pdfjs.GlobalWorkerOptions.workerPort = null;
}
configurePdfjsNodeRuntime(pdfjs);
const standardFontDataUrl = resolvePdfjsStandardFontDataUrl();

View file

@ -16,13 +16,18 @@ const securityHeaders = [
];
const bundleWorkerCompute = true;
const pdfjsTraceFiles = [
'./node_modules/pdfjs-dist/package.json',
'./node_modules/pdfjs-dist/legacy/build/pdf.mjs',
'./node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs',
'./node_modules/pdfjs-dist/standard_fonts/**/*',
];
const serverExternalPackages = [
'@napi-rs/canvas',
'better-sqlite3',
'ffmpeg-static',
// Keep pdfjs-dist out of the bundle: it resolves its own on-disk assets
// (standard_fonts) at runtime via require.resolve, which only works if it
// stays a real package in node_modules rather than being inlined.
// Keep pdfjs-dist as a real package in node_modules. Server-side preview
// rendering resolves pdf.js runtime assets from the filesystem at runtime.
'pdfjs-dist',
...(!bundleWorkerCompute ? ['onnxruntime-node', '@huggingface/tokenizers'] : []),
];
@ -56,19 +61,15 @@ const nextConfig: NextConfig = {
'./node_modules/ffmpeg-static/ffmpeg',
],
'/api/documents/blob/preview/ensure': [
// standard_fonts + the worker are loaded by path/specifier, not a static
// import, so trace them explicitly. The rest of pdfjs-dist is pulled in
// automatically now that it is a server-external package.
'./node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs',
'./node_modules/pdfjs-dist/standard_fonts/**/*',
// pdf.js runtime assets are resolved through filesystem paths at runtime,
// so trace them explicitly for Vercel/standalone serverless bundles.
...pdfjsTraceFiles,
],
'/api/documents/blob/preview/presign': [
'./node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs',
'./node_modules/pdfjs-dist/standard_fonts/**/*',
...pdfjsTraceFiles,
],
'/api/documents/blob/preview/fallback': [
'./node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs',
'./node_modules/pdfjs-dist/standard_fonts/**/*',
...pdfjsTraceFiles,
],
},
outputFileTracingExcludes: {