mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-11-13 12:13:42 +01:00
Fix PDF preview worker initialization (#704)
This commit is contained in:
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}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
Reference in new issue
Block a user