import { defineConfig } from "vite"; import solidPlugin from "vite-plugin-solid"; import { crx } from "@crxjs/vite-plugin"; import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { resolve } from "path"; const target = process.env.BUILD_TARGET || "chrome"; const manifestPath = target === "firefox" ? "./manifest.firefox.json" : "./manifest.json"; const manifest = JSON.parse(readFileSync(manifestPath, "utf-8")); if (target === "chrome") { manifest.web_accessible_resources = [{ matches: [""], resources: [""], }]; } const passkeyE2E = process.env.PEACH_E2E_PASSKEYS === "1"; const outDir = target === "firefox" ? "dist-firefox" : "dist-chrome"; // Copy the pdf.js worker into the build output so the popup can load it via // chrome.runtime.getURL('pdf.worker.min.mjs'). The pdfjs-dist module's default // workerSrc points inside node_modules, which is not reachable from a packaged // extension — the worker file must sit alongside the rest of the assets. const copyPdfJsWorker = { name: "copy-pdfjs-worker", closeBundle() { mkdirSync(outDir, { recursive: true }); copyFileSync( resolve("node_modules/pdfjs-dist/build/pdf.worker.min.mjs"), resolve(outDir, "pdf.worker.min.mjs"), ); if (target === "chrome") { const outputManifest = resolve(outDir, "manifest.json"); const builtManifest = JSON.parse(readFileSync(outputManifest, "utf8")); writeFileSync(outputManifest, `${JSON.stringify(builtManifest, null, 2)}\n`); } }, }; export default defineConfig({ plugins: [ solidPlugin(), crx({ manifest, browser: target === "firefox" ? "firefox" : "chrome" }), copyPdfJsWorker, ], base: "./", define: { __PEACH_E2E_PASSKEYS__: JSON.stringify(passkeyE2E), __PEACH_DEBUG__: false, }, build: { outDir, // emptyOutDir must stay false. When true, Vite tries to remove the // contents of the outDir before each build, which throws EPERM on FUSE // and some mounted filesystems and aborts the whole build. Overwriting // in place works everywhere. emptyOutDir: false, // The MV3 service worker has no `document`. Vite's module-preload helper // injects via document.getElementsByTagName, and // it runs synchronously inside every dynamic import() that carries chunk // deps. In the worker that throws `ReferenceError: document is not defined` // synchronously — escaping the import's own .catch() and tearing down the // caller (e.g. passkey-get persisting signCount via fireSyncTrigger). // Disabling preload makes the helper a no-op; the link hints are only a // page-load micro-optimisation and the popup loads fine without them. modulePreload: false, target: "esnext", rollupOptions: { // HV-2: the offscreen document (Chrome only — Firefox has no // chrome.offscreen) must be bundled and emitted so the background can // load it via chrome.offscreen.createDocument. Firefox capability-checks // chrome.offscreen and never references it. input: target === "firefox" ? undefined : { offscreen: resolve("src/background/offscreen.html") }, output: { manualChunks(id) { if (id.includes("node_modules/solid-js")) return "vendor-solid"; if (id.includes("node_modules/otpauth")) return "vendor-otp"; if (id.includes("node_modules/papaparse")) return "vendor-parse"; }, }, }, }, });