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:
parent
2f99b3987e
commit
0e29450b0d
4 changed files with 60 additions and 26 deletions
|
|
@ -3,7 +3,7 @@ import type { ParsedPdfDocument, ParsedPdfPage } from '../types/parsed-pdf';
|
||||||
import { ensureModel } from './model';
|
import { ensureModel } from './model';
|
||||||
import { runLayoutModel } from './runLayoutModel';
|
import { runLayoutModel } from './runLayoutModel';
|
||||||
import { mergeTextWithRegions } from './merge';
|
import { mergeTextWithRegions } from './merge';
|
||||||
import { resolvePdfjsStandardFontDataUrl } from './pdfjs-runtime';
|
import { configurePdfjsNodeRuntime, resolvePdfjsStandardFontDataUrl } from './pdfjs-runtime';
|
||||||
import { PDF_PARSER_VERSION } from './parser-version';
|
import { PDF_PARSER_VERSION } from './parser-version';
|
||||||
import { stitchCrossPageBlocks } from './stitch';
|
import { stitchCrossPageBlocks } from './stitch';
|
||||||
import { renderPage } from './render';
|
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
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
||||||
if (pdfjs.GlobalWorkerOptions) {
|
configurePdfjsNodeRuntime(pdfjs);
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.mjs';
|
|
||||||
pdfjs.GlobalWorkerOptions.workerPort = null;
|
|
||||||
}
|
|
||||||
const standardFontDataUrl = resolvePdfjsStandardFontDataUrl();
|
const standardFontDataUrl = resolvePdfjsStandardFontDataUrl();
|
||||||
|
|
||||||
const loadingTask = pdfjs.getDocument({
|
const loadingTask = pdfjs.getDocument({
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,39 @@
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { createRequire } from 'node:module';
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { pathToFileURL } from 'node:url';
|
||||||
|
|
||||||
let cachedStandardFontDataUrl: string | null = null;
|
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 {
|
export function resolvePdfjsStandardFontDataUrl(): string {
|
||||||
if (cachedStandardFontDataUrl) return cachedStandardFontDataUrl;
|
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');
|
const standardFontDir = path.join(pdfjsPackageDir, 'standard_fonts');
|
||||||
|
|
||||||
if (!fs.existsSync(standardFontDir)) {
|
if (!fs.existsSync(standardFontDir)) {
|
||||||
|
|
@ -18,3 +43,18 @@ export function resolvePdfjsStandardFontDataUrl(): string {
|
||||||
cachedStandardFontDataUrl = `${standardFontDir.replace(/\/?$/, '/')}`;
|
cachedStandardFontDataUrl = `${standardFontDir.replace(/\/?$/, '/')}`;
|
||||||
return cachedStandardFontDataUrl;
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Canvas } from '@napi-rs/canvas';
|
import type { Canvas } from '@napi-rs/canvas';
|
||||||
import { resolvePdfjsStandardFontDataUrl } from './pdfjs-runtime';
|
import { configurePdfjsNodeRuntime, resolvePdfjsStandardFontDataUrl } from './pdfjs-runtime';
|
||||||
|
|
||||||
type CanvasRuntime = {
|
type CanvasRuntime = {
|
||||||
DOMMatrixCtor: unknown;
|
DOMMatrixCtor: unknown;
|
||||||
|
|
@ -106,11 +106,7 @@ export async function renderPage({
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
||||||
|
configurePdfjsNodeRuntime(pdfjs);
|
||||||
if (pdfjs.GlobalWorkerOptions) {
|
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.mjs';
|
|
||||||
pdfjs.GlobalWorkerOptions.workerPort = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const standardFontDataUrl = resolvePdfjsStandardFontDataUrl();
|
const standardFontDataUrl = resolvePdfjsStandardFontDataUrl();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,18 @@ const securityHeaders = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const bundleWorkerCompute = true;
|
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 = [
|
const serverExternalPackages = [
|
||||||
'@napi-rs/canvas',
|
'@napi-rs/canvas',
|
||||||
'better-sqlite3',
|
'better-sqlite3',
|
||||||
'ffmpeg-static',
|
'ffmpeg-static',
|
||||||
// Keep pdfjs-dist out of the bundle: it resolves its own on-disk assets
|
// Keep pdfjs-dist as a real package in node_modules. Server-side preview
|
||||||
// (standard_fonts) at runtime via require.resolve, which only works if it
|
// rendering resolves pdf.js runtime assets from the filesystem at runtime.
|
||||||
// stays a real package in node_modules rather than being inlined.
|
|
||||||
'pdfjs-dist',
|
'pdfjs-dist',
|
||||||
...(!bundleWorkerCompute ? ['onnxruntime-node', '@huggingface/tokenizers'] : []),
|
...(!bundleWorkerCompute ? ['onnxruntime-node', '@huggingface/tokenizers'] : []),
|
||||||
];
|
];
|
||||||
|
|
@ -56,19 +61,15 @@ const nextConfig: NextConfig = {
|
||||||
'./node_modules/ffmpeg-static/ffmpeg',
|
'./node_modules/ffmpeg-static/ffmpeg',
|
||||||
],
|
],
|
||||||
'/api/documents/blob/preview/ensure': [
|
'/api/documents/blob/preview/ensure': [
|
||||||
// standard_fonts + the worker are loaded by path/specifier, not a static
|
// pdf.js runtime assets are resolved through filesystem paths at runtime,
|
||||||
// import, so trace them explicitly. The rest of pdfjs-dist is pulled in
|
// so trace them explicitly for Vercel/standalone serverless bundles.
|
||||||
// automatically now that it is a server-external package.
|
...pdfjsTraceFiles,
|
||||||
'./node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs',
|
|
||||||
'./node_modules/pdfjs-dist/standard_fonts/**/*',
|
|
||||||
],
|
],
|
||||||
'/api/documents/blob/preview/presign': [
|
'/api/documents/blob/preview/presign': [
|
||||||
'./node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs',
|
...pdfjsTraceFiles,
|
||||||
'./node_modules/pdfjs-dist/standard_fonts/**/*',
|
|
||||||
],
|
],
|
||||||
'/api/documents/blob/preview/fallback': [
|
'/api/documents/blob/preview/fallback': [
|
||||||
'./node_modules/pdfjs-dist/legacy/build/pdf.worker.mjs',
|
...pdfjsTraceFiles,
|
||||||
'./node_modules/pdfjs-dist/standard_fonts/**/*',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
outputFileTracingExcludes: {
|
outputFileTracingExcludes: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue