Fix PDF preview worker initialization (#704)

This commit is contained in:
Gregory Schier authored and GitHub committed 2026-09-23 07:41:55 -07:00
1 parent 4913711968
commit 646d969649
2 files changed
+110 -10

No files matched your search

@@ -3,18 +3,15 @@ import "react-pdf/dist/Page/AnnotationLayer.css";
import "./PdfViewer.css";
import type { PDFDocumentProxy } from "pdfjs-dist";
import { useMemo, useRef, useState } from "react";
import { Document, Page } from "react-pdf";
import { Document, Page, pdfjs } from "react-pdf";
import { useContainerSize } from "@yaakapp-internal/ui";
import { fireAndForget } from "../../lib/fireAndForget";
fireAndForget(
import("react-pdf").then(({ pdfjs }) => {
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
}),
);
// Document can start loading during render, so configure its worker synchronously.
// Vite bundles the installed PDF.js worker as a local asset, including offline builds.
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
interface Props {
/** A URL for the body the host already stored. */
@@ -59,6 +56,8 @@ export function PdfViewer({ bodyUrl, data }: Props) {
return (
<div ref={containerRef} className="w-full h-full overflow-y-auto">
<Document
// Keep the copied data stable: suspending the initial mount discards useMemo.
suspense={false}
file={src}
options={options}
onLoadSuccess={onDocumentLoadSuccess}
+101
View File
@@ -0,0 +1,101 @@
import { readFile } from "node:fs/promises";
import { createRequire } from "node:module";
import path from "node:path";
import vm from "node:vm";
import { build } from "vite";
import { expect, test } from "vitest";
const root = path.resolve(import.meta.dirname, "..");
const viewer = path.join(root, "apps/yaak-client/components/responseViewers/PdfViewer.tsx");
test.each(["http://tauri.localhost", "tauri://localhost"])(
"PDF viewer configures a matching bundled worker before rendering on %s",
async (origin) => {
const result = await build({
configFile: false,
define: { "process.env.NODE_ENV": JSON.stringify("production") },
root,
logLevel: "silent",
resolve: {
// Keep the real sizing hook without pulling unrelated UI/platform code into this fixture.
alias: {
"@yaakapp-internal/ui": path.join(root, "packages/ui/src/hooks/useContainerSize.ts"),
},
},
plugins: [
{
name: "pdf-viewer-test-entry",
resolveId(id) {
if (id === "pdf-viewer-test") return id;
},
load(id) {
if (id !== "pdf-viewer-test") return;
return `
import { PdfViewer } from ${JSON.stringify(viewer)};
import { pdfjs } from 'react-pdf';
globalThis.pdfViewerTest = { PdfViewer, workerSrc: pdfjs.GlobalWorkerOptions.workerSrc, version: pdfjs.version };
`;
},
},
],
build: {
target: "esnext",
modulePreload: false,
cssCodeSplit: false,
write: false,
minify: true,
rolldownOptions: {
input: "pdf-viewer-test",
output: { format: "es", codeSplitting: false },
},
},
});
if (Array.isArray(result) || !("output" in result)) throw new Error("Expected one bundle");
const entry = result.output.find((chunk) => chunk.type === "chunk" && chunk.isEntry);
if (!entry || entry.type !== "chunk") throw new Error("Missing viewer entry");
// Evaluate as a browser (no Node process), and inspect immediately, before promise callbacks.
// This checks initialization timing and assets, not rendering or native asset protocols.
const context = vm.createContext({
URL,
navigator: { userAgent: "" },
DOMMatrix: class {},
window: { requestAnimationFrame: () => {}, location: new URL(`${origin}/`) },
document: {
documentElement: { style: {} },
createElement: () => ({ style: {} }),
addEventListener: () => {},
},
console,
self: { location: { href: `${origin}/index.html` } },
});
// Supply the module URL to the script VM without changing the emitted asset path.
vm.runInContext(
entry.code.replaceAll(
"import.meta",
`({ url: ${JSON.stringify(`${origin}/assets/entry.js`)} })`,
),
context,
);
const { workerSrc, version } = context.pdfViewerTest as { workerSrc: string; version: string };
expect(workerSrc).toContain("/assets/");
const workerUrl = new URL(workerSrc);
expect(workerUrl.protocol).toBe(new URL(origin).protocol);
expect(workerUrl.host).toBe(new URL(origin).host);
const worker = result.output.find((asset) => `/${asset.fileName}` === workerUrl.pathname);
expect(worker?.type).toBe("asset");
if (!worker || worker.type !== "asset")
throw new Error("Worker URL missing from production assets");
// Resolve from React-PDF itself so a second, mismatched PDF.js installation is caught.
const require = createRequire(path.join(root, "apps/yaak-client/package.json"));
const reactPdfRequire = createRequire(require.resolve("react-pdf"));
const pdfjsDir = path.dirname(reactPdfRequire.resolve("pdfjs-dist/package.json"));
const installed = JSON.parse(await readFile(path.join(pdfjsDir, "package.json"), "utf8"));
expect(version).toBe(installed.version);
expect(Buffer.from(worker.source)).toEqual(
await readFile(path.join(pdfjsDir, "build/pdf.worker.min.mjs")),
);
},
30_000,
);