Ctrl K
ComunidadGuías…

vite.config.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import { cpSync, createReadStream, existsSync, statSync } from "node:fs";
import path from "node:path";
import { defineConfig, type Plugin } from "vite";
import { readFileSync } from "node:fs";
import react from "@vitejs/plugin-react";
import { chunkFor } from "./build/chunks.ts";

// ── pdf.js side data ───────────────────────────────────────────────────────
// pdf.js is not one file. Four directories ship beside it and each answers a
// question the library cannot answer from the PDF alone:
//
//   cmaps/          how a CJK font's byte sequences map to Unicode. Absent,
//                   a Japanese book renders as boxes.
//   standard_fonts/ the base-14 fonts (Helvetica, Times, …) that a PDF is
//                   allowed to REFERENCE without embedding. A great many
//                   documents do exactly that.
//   wasm/           the JBIG2 and JPEG 2000 decoders. Every scanned book in
//                   the world is one of those two formats.
//   iccs/           the colour profile used to render CMYK correctly.
//
// They are files fetched at runtime by URL, not modules, so bundling cannot
// reach them: rollup would have to know that a string built inside pdf.js
// names a file on disk. So they are copied verbatim to `/pdfjs/…` — a real,
// same-origin path under `connect-src 'self'` — and served from node_modules
// in dev by the middleware below, which is what keeps the dev server and the
// built site agreeing about a URL that would otherwise only exist in one of
// them. (The one thing this plugin deliberately does NOT handle is the pdf.js
// WORKER: that is a module, it is imported with `?url` from
// client/books/pdfjs.ts so the build hashes and emits it like any other asset,
// and `npm run check-books` asserts it never becomes a `blob:` — see the CSP
// note in server/index.ts.)
const PDFJS_DIRS = ["cmaps", "standard_fonts", "wasm", "iccs"];

/** Where the URL prefix lives, both in dev and under dist/. Spelled again
 *  in client/books/pdfjs.ts (a client module cannot import this file); the two
 *  literals are asserted equal by scripts/check-books.mjs. */
const PDFJS_BASE = "/pdfjs/";

function pdfjsAssets(): Plugin {
  const require = createRequire(import.meta.url);
  const root = path.dirname(require.resolve("pdfjs-dist/package.json"));
  return {
    name: "astrolabe:pdfjs-assets",
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        const url = (req.url ?? "").split("?")[0];
        if (!url.startsWith(PDFJS_BASE)) return next();
        // Resolved and then re-checked against the root: this is dev-only
        // code, but it is still a path from a URL, and "dev only" is how a
        // traversal ships.
        const abs = path.resolve(root, decodeURIComponent(url.slice(PDFJS_BASE.length)));
        if (!abs.startsWith(root + path.sep) || !existsSync(abs) || !statSync(abs).isFile()) {
          return next();
        }
        res.setHeader("Content-Type", abs.endsWith(".wasm") ? "application/wasm" : "application/octet-stream");
        createReadStream(abs).pipe(res);
      });
    },
    // After the bundle is on disk, not during it: these are opaque data files
    // with no place in the module graph, and emitting them as assets would
    // content-hash names that pdf.js builds by string concatenation.
    closeBundle() {
      const out = path.resolve(fileURLToPath(new URL(".", import.meta.url)), "dist", "pdfjs");
      for (const dir of PDFJS_DIRS) {
        const from = path.join(root, dir);
        if (existsSync(from)) cpSync(from, path.join(out, dir), { recursive: true });
      }
    },
  };
}

// ── Excalidraw's fonts ──────────────────────────────────────────────────────
// The drawing surface's package resolves its typefaces (Excalifont, Virgil,
// Cascadia, the CJK and Arabic fallbacks) at runtime from
// `window.EXCALIDRAW_ASSET_PATH` — a URL, not a module — and would otherwise
// go to a CDN, which `connect-src 'self'` refuses and a self-hosted product
// must not want. Same arrangement as pdf.js above: copied verbatim to
// `/excalidraw/fonts/…` under dist/, served from node_modules in dev, and the
// prefix is spelled again in client/drawing/assetPath.ts.
const EXCALIDRAW_BASE = "/excalidraw/";

function excalidrawAssets(): Plugin {
  const require = createRequire(import.meta.url);
  // The package's exports map hides its package.json, so the root is found
  // from the entry module: …/dist/<dev|prod>/index.js → …/dist/prod.
  const entry = require.resolve("@excalidraw/excalidraw").replace(/\\/g, "/");
  const root = path.join(entry.slice(0, entry.lastIndexOf("/dist/")), "dist", "prod");
  return {
    name: "astrolabe:excalidraw-assets",
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        const url = (req.url ?? "").split("?")[0];
        if (!url.startsWith(EXCALIDRAW_BASE)) return next();
        const abs = path.resolve(root, decodeURIComponent(url.slice(EXCALIDRAW_BASE.length)));
        if (!abs.startsWith(root + path.sep) || !existsSync(abs) || !statSync(abs).isFile()) {
          return next();
        }
        res.setHeader("Content-Type", abs.endsWith(".woff2") ? "font/woff2" : "application/octet-stream");
        createReadStream(abs).pipe(res);
      });
    },
    closeBundle() {
      const out = path.resolve(fileURLToPath(new URL(".", import.meta.url)), "dist", "excalidraw");
      const from = path.join(root, "fonts");
      if (existsSync(from)) cpSync(from, path.join(out, "fonts"), { recursive: true });
    },
  };
}

export default defineConfig({
  // The build's own version, so a tab can tell when the server has moved on
  // (client/state.ts loadMe compares it with /api/me's `version`).
  define: { __APP_VERSION__: JSON.stringify(JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")).version) },
  plugins: [react(), pdfjsAssets(), excalidrawAssets()],
  root: "client",
  build: {
    outDir: "../dist",
    emptyOutDir: true,
    // The server reads dist/.vite/manifest.json to name the chunks the shell
    // a given request will mount, and preloads exactly those (server/preload.ts).
    manifest: true,
    // Deterministic chunk boundaries — see build/chunks.ts for the policy and
    // why each boundary exists. Without this, rollup's automatic splitting
    // put every component (both shells, the graph engine, the settings and
    // moderation modals) into the single entry chunk that an anonymous blog
    // reader downloads before the first paragraph renders.
    rollupOptions: { output: { manualChunks: chunkFor } },
  },
  server: {
    port: 5801,
    proxy: {
      "/api": { target: "http://localhost:6801", changeOrigin: true },
    },
  },
});