Replace the vite-multiple-assets dev dependency with a small custom Vite plugin (#3976)

* Replace the vite-multiple-assets dev dependency with a small custom Vite plugin

* Restore exception
This commit is contained in:
Keavon Chambers 2026-03-29 03:33:35 -07:00
parent a3ea6ab0af
commit 203910a92f
3 changed files with 242 additions and 884 deletions

File diff suppressed because it is too large Load Diff

View File

@ -52,8 +52,7 @@
"tar": "^7.5.12", "tar": "^7.5.12",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.57.1", "typescript-eslint": "^8.57.1",
"vite": "^8.0.1", "vite": "^8.0.1"
"vite-multiple-assets": "2.2.6"
}, },
"homepage": "https://graphite.art", "homepage": "https://graphite.art",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -1,17 +1,16 @@
import { execSync } from "child_process"; import { execSync } from "child_process";
import { readFileSync } from "fs"; import { copyFileSync, cpSync, existsSync, readFileSync, statSync } from "fs";
import path from "path"; import path from "path";
import { svelte } from "@sveltejs/vite-plugin-svelte"; import { svelte } from "@sveltejs/vite-plugin-svelte";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import type { PluginOption } from "vite"; import type { PluginOption } from "vite";
import { DynamicPublicDirectory as viteMultipleAssets } from "vite-multiple-assets";
const projectRootDir = path.resolve(__dirname); const projectRootDir = path.resolve(__dirname);
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
return { return {
plugins: plugins(mode), plugins: [svelte(), staticAssets(), mode !== "native" && thirdPartyLicenses()],
resolve: { resolve: {
alias: [{ find: /\/..\/branding\/(.*\.svg)/, replacement: path.resolve(projectRootDir, "../branding", "$1?raw") }], alias: [{ find: /\/..\/branding\/(.*\.svg)/, replacement: path.resolve(projectRootDir, "../branding", "$1?raw") }],
}, },
@ -22,42 +21,78 @@ export default defineConfig(({ mode }) => {
}; };
}); });
function plugins(mode: string): PluginOption[] { function staticAssets(): PluginOption {
const plugins = [ const STATIC_ASSET_DIRS: { source: string; urlPrefix: string }[] = [
svelte(), { source: "../demo-artwork", urlPrefix: "/demo-artwork" },
viteMultipleAssets( { source: "../branding/favicons", urlPrefix: "" },
// Additional static asset directories
[
{ input: "../demo-artwork/**", output: "demo-artwork" },
{ input: "../branding/favicons/**", output: "" },
],
// Options where we set custom MIME types
{ mimeTypes: { ".graphite": "application/json" } },
),
]; ];
if (mode !== "native") { // MIME types for all file extensions found in the static asset directories
plugins.push({ const MIME_TYPES: Record<string, string> = {
name: "third-party-licenses", ".graphite": "application/json",
buildStart() { ".ico": "image/x-icon",
try { ".png": "image/png",
execSync("cargo run -p third-party-licenses", { ".svg": "image/svg+xml",
stdio: "inherit", ".webmanifest": "application/manifest+json",
}); ".xml": "application/xml",
} catch (_e) { };
this.error("Failed to generate third-party licenses");
}
},
generateBundle() {
const source = readFileSync(path.resolve(projectRootDir, "third-party-licenses.txt"), "utf-8");
this.emitFile({
type: "asset",
fileName: "third-party-licenses.txt",
source,
});
},
});
}
return plugins; return {
name: "static-assets",
// Dev: serve files from the listed directories via middleware
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (!req.url) return next();
const urlPath = decodeURIComponent(req.url.split("?")[0]);
const match = STATIC_ASSET_DIRS.find(({ source, urlPrefix }) => {
if (urlPrefix && !urlPath.startsWith(urlPrefix + "/") && urlPath !== urlPrefix) return false;
if (!urlPrefix && urlPath.startsWith("/@")) return false;
const relativePath = urlPrefix ? urlPath.slice(urlPrefix.length) : urlPath;
const filePath = path.resolve(projectRootDir, source, "." + relativePath);
const sourceDir = path.resolve(projectRootDir, source) + path.sep;
if (!filePath.startsWith(sourceDir)) return false;
return existsSync(filePath) && !statSync(filePath).isDirectory();
});
if (!match) return next();
const { source, urlPrefix } = match;
const relativePath = urlPrefix ? urlPath.slice(urlPrefix.length) : urlPath;
const filePath = path.resolve(projectRootDir, source, "." + relativePath);
const extension = path.extname(filePath).toLowerCase();
const contentType = MIME_TYPES[extension] || "application/octet-stream";
res.setHeader("Content-Type", contentType);
res.end(readFileSync(filePath));
});
},
// Build: copy the listed directories into the output
writeBundle(options) {
STATIC_ASSET_DIRS.forEach(({ source, urlPrefix }) => {
const sourceDir = path.resolve(projectRootDir, source);
const destinationDir = path.join(options.dir || "dist", urlPrefix);
if (existsSync(sourceDir)) cpSync(sourceDir, destinationDir, { recursive: true });
});
},
};
}
function thirdPartyLicenses(): PluginOption {
return {
name: "third-party-licenses",
buildStart() {
try {
execSync("cargo run -p third-party-licenses", { stdio: "inherit" });
} catch (e) {
throw new Error("Failed to generate third-party licenses", { cause: e });
}
},
writeBundle(options) {
copyFileSync(path.resolve(projectRootDir, "third-party-licenses.txt"), path.join(options.dir || "dist", "third-party-licenses.txt"));
},
};
} }