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); }); }, }; } // Materialize requested bytes over HTTP without checking a 64 MiB performance-only fixture into the repository. const sparseBundleBytes = 64 * 1024 * 1024; const sparseChunkBytes = 4 * 1024 * 1024; const sparseStreamBytes = 64 * 1024; const sparseChunkSha256 = "bb9f8df61474d25e71fa00722318cd387396ca1736605e1248821cc0de3d3af8"; const sparseBundleSha256 = "3b6a07d0d404fab4e23b6d34bc6696a6a312dd92821332385e5af7c01c421351"; const sparseConverterSha256 = "0000000000000000000000000000000000000000000000000000000000000000"; function sparseBundleManifest(): Record { return { schemaVersion: 1, projectId: "vdb-sparse-64m", sourcePath: "//volumes/generated-synthetic-sparse.vdb", sourceSha256: sparseBundleSha256, conversionRequestSha256: sparseConverterSha256, bundlePath: "//volumes/generated-sparse-64m.nvdb", bundleByteLength: sparseBundleBytes, bundleSha256: sparseBundleSha256, converter: { target: "DESKTOP", blenderVersion: "5.2.0", openVDBVersion: "13.0.0", nanoVDBVersion: "32.9.0", executableSha256: sparseConverterSha256, }, grids: [{ name: "density", valueType: "FLOAT16", gridClass: "FOG_VOLUME", semantic: "DENSITY", activeVoxelCount: 0, segmentByteOffset: 0, segmentByteLength: sparseBundleBytes, byteOffset: 0, byteLength: sparseBundleBytes, indexBounds: { min: [0, 0, 0], max: [0, 0, 0] }, worldBounds: { min: [0, 0, 0], max: [0, 0, 0] }, voxelSize: [1, 1, 1], indexToWorld: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], }], chunks: Array.from({ length: sparseBundleBytes / sparseChunkBytes }, (_, index) => ({ index, byteOffset: index * sparseChunkBytes, byteLength: sparseChunkBytes, sha256: sparseChunkSha256, })), material: { densityGrid: "density", densityScale: 1, emissionScale: 0, temperatureScale: 1, anisotropy: 0, interpolation: "NEAREST", }, gpu: { representation: "NANOVDB_STORAGE_BUFFER", byteAlignment: 32, pageByteLength: 256 * 1024, maxResidentBytes: 1 * 1024 * 1024, shaderSemanticVersion: "volume-wgsl-v1", }, }; } function serveSparseVDBFixture(): Plugin { return { name: "serve-sparse-64m-vdb-fixture", configureServer(server) { server.middlewares.use((request, response, next) => { const nodeRequest = request as unknown as { url?: string; headers: { range?: string } }; const parsed = new URL(nodeRequest.url ?? "/", "http://vite.local"); if (parsed.pathname !== "/__vdb_sparse_64m__/manifest" && parsed.pathname !== "/__vdb_sparse_64m__/bundle") { next(); return; } const output = response as unknown as { statusCode: number; setHeader: (name: string, value: string | number) => void; writeHead: (status: number) => void; write: (data: Uint8Array) => boolean; end: (data?: string) => void; on: (event: string, listener: () => void) => void; once: (event: string, listener: () => void) => void; }; if (parsed.pathname.endsWith("/manifest")) { const body = JSON.stringify(sparseBundleManifest()); output.statusCode = 200; output.setHeader("Content-Type", "application/json"); output.setHeader("Cache-Control", "no-store"); output.setHeader("Content-Length", body.length); output.end(body); return; } const match = nodeRequest.headers.range?.match(/^bytes=(\d+)-(\d+)$/); const start = match ? Number(match[1]) : 0; const end = match ? Number(match[2]) : sparseBundleBytes - 1; if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end >= sparseBundleBytes) { output.statusCode = 416; output.end(); return; } const length = end - start + 1; const delayMs = Math.min(25, Math.max(0, Number.parseInt(parsed.searchParams.get("delayMs") ?? "0", 10) || 0)); output.statusCode = match ? 206 : 200; output.setHeader("Content-Type", "application/x-nanovdb"); output.setHeader("Accept-Ranges", "bytes"); output.setHeader("Cache-Control", "no-store"); output.setHeader("ETag", '"vdb-sparse-64m-v1"'); output.setHeader("Content-Length", length); if (match) output.setHeader("Content-Range", `bytes ${start}-${end}/${sparseBundleBytes}`); let closed = false; let written = 0; const zeroes = new Uint8Array(sparseStreamBytes); output.on("close", () => { closed = true; }); const pump = (): void => { if (closed) return; if (written >= length) { output.end(); return; } const nextLength = Math.min(sparseStreamBytes, length - written); const accepted = output.write(nextLength === zeroes.byteLength ? zeroes : zeroes.slice(0, nextLength)); written += nextLength; const continuePump = (): void => { if (!closed) setTimeout(pump, delayMs); }; if (!accepted) output.once("drain", continuePump); else continuePump(); }; pump(); }); }, }; } export default defineConfig({ root: "app", plugins: [ serveEngineVariantModuleImports(), ...(unisolatedSingleThreadTest ? [] : [preserveIsolationHeaders()]), localVDBFixture(), serveSparseVDBFixture(), react(), ], server: { port: 5173, strictPort: false, headers: developmentHeaders, }, preview: { headers: developmentHeaders, }, build: { outDir: "../dist", emptyOutDir: true, }, });