import { defineConfig, type Plugin } from "vite"; import react from "@vitejs/plugin-react"; // @ts-expect-error The runtime is Node; this project intentionally avoids a browser dependency on Node types. import fs from "node:fs"; // @ts-expect-error The runtime is Node; this project intentionally avoids a browser dependency on Node types. import path from "node:path"; // @ts-expect-error The runtime is Node; this project intentionally avoids a browser dependency on Node types. import { fileURLToPath } from "node:url"; const deploymentContract = JSON.parse( fs.readFileSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../docs/web/deployment-contract.json"), "utf8"), ) as { responseHeaders: { allResponses: Record } }; const isolationHeaders = deploymentContract.responseHeaders.allResponses; const runtimeEnvironment = (globalThis as { process?: { env?: Record } }).process?.env; const unisolatedSingleThreadTest = runtimeEnvironment?.WEB_TEST_UNISOLATED_SINGLE_THREAD === "1"; const developmentHeaders = unisolatedSingleThreadTest ? {} : isolationHeaders; function preserveIsolationHeaders(): Plugin { const install = (server: { middlewares: { use: (handler: (request: unknown, response: { setHeader: (name: string, value: string) => void }, next: () => void) => void) => void } }) => { server.middlewares.use((_request, response, next) => { for (const [name, value] of Object.entries(isolationHeaders)) response.setHeader(name, value); next(); }); }; return { name: "preserve-isolation-headers", configureServer: install, configurePreviewServer: install, }; } function serveEngineVariantModuleImports(): Plugin { const vendorRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "public/vendor/blender"); const install = (server: { middlewares: { use: (handler: (request: unknown, response: { setHeader: (name: string, value: string | number) => void }, next: () => void) => void) => void } }) => { server.middlewares.use((request, response, next) => { const url = new URL((request as { url?: string }).url ?? "/", "http://vite.local"); const match = url.pathname.match(/^\/vendor\/blender\/(single|pthread)\/web_engine\.js$/); if (!match || !url.searchParams.has("import")) { next(); return; } const filePath = path.join(vendorRoot, match[1], "web_engine.js"); if (!fs.existsSync(filePath)) { next(); return; } const stat = fs.statSync(filePath); response.setHeader("Content-Type", "text/javascript; charset=utf-8"); response.setHeader("Content-Length", stat.size); for (const [name, value] of Object.entries(developmentHeaders)) response.setHeader(name, value); fs.createReadStream(filePath).pipe(response as never); }); }; return { name: "serve-engine-variant-module-imports", configureServer: install, configurePreviewServer: install, }; } function localVDBFixture(): Plugin { const resourceRoot = runtimeEnvironment?.VDB_RESOURCE_ROOT ?? path.join(runtimeEnvironment?.HOME ?? "", "resource-library/blender-web-vdb"); const files = new Map([ ["/__vdb_fixture__/manifest", { path: path.join(resourceRoot, "manifests/generated-smoke.nanovdb.json"), type: "application/json" }], ["/__vdb_fixture__/report", { path: path.join(resourceRoot, "reports/generated-smoke-conversion.json"), type: "application/json" }], ["/__vdb_fixture__/bundle", { path: path.join(resourceRoot, "nanovdb/generated-smoke.nvdb"), type: "application/x-nanovdb" }], ["/assets/volumes/generated-smoke.nanovdb.json", { path: path.join(resourceRoot, "manifests/generated-smoke.nanovdb.json"), type: "application/json" }], ["/assets/volumes/generated-smoke.nvdb", { path: path.join(resourceRoot, "nanovdb/generated-smoke.nvdb"), type: "application/x-nanovdb" }], ["/volumes/generated-smoke.nanovdb.json", { path: path.join(resourceRoot, "manifests/generated-smoke.nanovdb.json"), type: "application/json" }], ["/volumes/generated-smoke.nvdb", { path: path.join(resourceRoot, "nanovdb/generated-smoke.nvdb"), type: "application/x-nanovdb" }], ]); return { name: "local-vdb-fixture", configureServer(server) { server.middlewares.use((request, response, next) => { const nodeRequest = request as unknown as { url?: string; headers: { range?: string } }; const pathname = nodeRequest.url?.split("?", 1)[0] ?? ""; const fixture = files.get(pathname); if (!fixture || !fs.existsSync(fixture.path)) { next(); return; } const stat = fs.statSync(fixture.path); response.setHeader("Content-Type", fixture.type); response.setHeader("Accept-Ranges", "bytes"); response.setHeader("Cache-Control", "no-store"); response.setHeader("ETag", `"vdb-${stat.size}-${Math.trunc(stat.mtimeMs)}"`); const match = nodeRequest.headers.range?.match(/^bytes=(\d+)-(\d+)$/); if (match) { const start = Number(match[1]); const end = Number(match[2]); if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end >= stat.size) { response.writeHead(416); response.end(); return; } response.statusCode = 206; response.setHeader("Content-Range", `bytes ${start}-${end}/${stat.size}`); response.setHeader("Content-Length", end - start + 1); fs.createReadStream(fixture.path, { start, end }).pipe(response); return; } response.setHeader("Content-Length", stat.size); fs.createReadStream(fixture.path).pipe(response); }); }, }; } export default defineConfig({ root: "app", plugins: [ serveEngineVariantModuleImports(), ...(unisolatedSingleThreadTest ? [] : [preserveIsolationHeaders()]), localVDBFixture(), react(), ], server: { port: 5173, strictPort: false, headers: developmentHeaders, }, preview: { headers: developmentHeaders, }, build: { outDir: "../dist", emptyOutDir: true, }, });