frontend/vite.config.ts
Rama main · réplica de lectura del repositorio original.
import { defineConfig, type Plugin } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
import fs from "fs";
import { createHash } from "node:crypto";
import { rnnoiseWasmPlugin } from "./scripts/rnnoise-wasm.mjs";
function manualChunks(id: string): string | undefined {
const normalized = id.split(path.sep).join("/");
if (normalized.endsWith("/src/data/tools.ts") || normalized.endsWith("/src/data/non-pdf-tools.ts")) {
return "tool-catalog";
}
if (!normalized.includes("/node_modules/")) {
return undefined;
}
if (
normalized.includes("/node_modules/react/") ||
normalized.includes("/node_modules/react-dom/") ||
normalized.includes("/node_modules/react-router") ||
normalized.includes("/node_modules/@remix-run/router/")
) {
return "vendor-react";
}
if (
normalized.includes("/node_modules/@radix-ui/react-tooltip/") ||
normalized.includes("/node_modules/@radix-ui/react-dialog/") ||
normalized.includes("/node_modules/@radix-ui/react-tabs/") ||
normalized.includes("/node_modules/@radix-ui/react-select/") ||
normalized.includes("/node_modules/@radix-ui/react-slot/")
) {
return "vendor-radix";
}
if (normalized.includes("/node_modules/lucide-react/")) {
return "vendor-icons";
}
return undefined;
}
/** Every deployed asset graph gets a fresh offline shell and an update notice. */
function serviceWorkerVersionPlugin(): Plugin {
let outputDirectory = path.resolve(__dirname, "dist");
return {
name: "privatools-service-worker-version",
apply: "build",
configResolved(config) {
outputDirectory = path.resolve(config.root, config.build.outDir);
},
writeBundle() {
const workerPath = path.join(outputDirectory, "sw.js");
const worker = fs.readFileSync(workerPath, "utf8");
const digest = createHash("sha256")
.update(fs.readFileSync(path.join(outputDirectory, "index.html")))
.update(fs.readdirSync(path.join(outputDirectory, "assets")).sort().join("\n"))
.update(fs.readFileSync(path.join(outputDirectory, "manifest.json")))
.update(worker);
for (const asset of [
"icons/icon-192.png", "icons/icon-512.png", "icons/icon-maskable-512.png",
"brand/privatools-icon-96.png", "brand/privatools-favicon.svg", "brand/privatools-icon-180.png",
"experience/air-mist.png", "experience/air-graphite.png",
]) {
digest.update(fs.readFileSync(path.join(outputDirectory, asset)));
}
const version = `v2.${digest.digest("hex").slice(0, 16)}`;
if (!worker.includes('const CACHE_VERSION = "v2.0.0";')) {
throw new Error("Service worker version marker is missing.");
}
fs.writeFileSync(workerPath, worker.replace('const CACHE_VERSION = "v2.0.0";', `const CACHE_VERSION = "${version}";`));
console.log(`[pwa] offline shell ${version}`);
},
};
}
export default defineConfig({
server: {
host: "::",
port: Number(process.env.PORT) || 8080,
strictPort: false,
hmr: { overlay: false },
proxy: {
"/api/": {
// Override when the backend runs somewhere else, e.g.
// VITE_DEV_API_TARGET=http://localhost:8001 npm run dev
target: process.env.VITE_DEV_API_TARGET || "http://localhost:8000",
changeOrigin: true,
},
},
},
// rnnoiseWasmPlugin: Voice Noise Remover's RNNoise WebAssembly, emitted as an asset of its own (scripts/rnnoise-wasm.mjs).
plugins: [react(), serviceWorkerVersionPlugin(), rnnoiseWasmPlugin()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
modulePreload: {
resolveDependencies: (_filename, deps) =>
deps.filter(dep => !/(^|\/)(vendor-icons|tool-catalog)-/.test(dep)),
},
rollupOptions: {
output: {
// Split large vendor libraries into separate cached chunks so
// returning users get them from the SW/CDN cache instead of
// re-downloading on every deploy. Vite 8/Rolldown requires the
// function form here; the object form Rollup accepted in Vite 5 now
// fails at build time.
manualChunks,
},
},
// Target modern browsers for smaller output
target: "es2020",
// The expected >600 kB JS chunks are lazy: the @huggingface/transformers
// runtime for AI PDF tools, and gpt-tokenizer's two rank tables for the
// AI Token Counter, which this warning names on every build
// (scripts/check-bundle-size.mjs gives them ceilings of their own). Keep
// the warning below 1 MB so accidental eager bundles still fail loudly.
chunkSizeWarningLimit: 900,
},
esbuild: {
// Drop dev-only console calls + debugger statements from the production
// bundle so accidental `console.log("foo", bigObject)` left in tool UIs
// doesn't ship to users. `console.error` / `console.warn` are kept so
// real failures still surface in production telemetry.
drop: ["debugger"],
pure: ["console.log", "console.debug", "console.trace"],
},
});