Add PUBLIC_DIR env var to merge external public files at build time

Files in the external directory take precedence over the built-in
public/ directory. Works in both dev (middleware) and build (post-copy)
modes. Also fix missing magicMouse property in TestApp.
This commit is contained in:
Alex Gleason
2026-02-28 15:29:29 -06:00
parent 2971139bbd
commit 01b071d1c3
2 changed files with 66 additions and 0 deletions
+65
View File
@@ -2,6 +2,7 @@ import fs from "node:fs";
import path from "node:path";
import react from "@vitejs/plugin-react-swc";
import type { Plugin } from "vite";
import { defineConfig } from "vitest/config";
import { DittoConfigSchema } from "./src/lib/schemas";
@@ -27,7 +28,70 @@ function loadDittoConfig(): object | undefined {
return result;
}
/**
* Copy all files from `src` into `dest`, overwriting existing files.
* Recursively handles subdirectories.
*/
function copyDirSync(src: string, dest: string): void {
fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirSync(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
/**
* Vite plugin that merges an external public directory on top of the default one.
* Set the PUBLIC_DIR env var to a directory path. Files in that directory take
* precedence over files in the built-in `public/` directory.
*
* - In build mode, files are copied into the output after the default public dir.
* - In dev mode, the external directory is served with higher priority.
*/
function mergePublicDir(externalDir: string): Plugin {
const resolved = path.resolve(externalDir);
return {
name: "ditto:merge-public-dir",
configureServer(server) {
// Serve files from the external public dir before the default public dir.
server.middlewares.use((req, res, next) => {
if (!req.url) return next();
const urlPath = decodeURIComponent(new URL(req.url, "http://localhost").pathname);
const filePath = path.join(resolved, urlPath);
try {
const stat = fs.statSync(filePath);
if (stat.isFile()) {
// Let Vite's static middleware handle it by pointing to the file.
const stream = fs.createReadStream(filePath);
stream.pipe(res);
return;
}
} catch {
// File not found in external dir — fall through to default public dir
}
next();
});
},
writeBundle(options) {
const outDir = options.dir ?? path.resolve("dist");
copyDirSync(resolved, outDir);
},
};
}
const dittoConfig = loadDittoConfig();
const publicDir = process.env.PUBLIC_DIR;
// https://vitejs.dev/config/
export default defineConfig(() => ({
@@ -37,6 +101,7 @@ export default defineConfig(() => ({
},
plugins: [
react(),
...(publicDir ? [mergePublicDir(publicDir)] : []),
],
define: {
__DITTO_CONFIG__: JSON.stringify(dittoConfig ?? null),